21

I am using provider in my app, but I faced with unnecessary building.

Example

class AllWidget extends StatelessWidget{

  @override
  Widget build(BuildContext context){
    print('state build called');
    return ChangeNotifierProvider(
            builder: (_) => MyCounter(),
            child: Column(children: <Widget>[
                  MyCounterText(),
                  MyIncreaseButton(),
                  MyDecreaseButton(),
            ],
          ),
    );
  }
}

class MyCounterText extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    final myCounter = Provider.of<MyCounter>(context, listen: false);
    print('MyCounterText');
    return Text(myCounter.num.toString());

  }
}

class MyIncreaseButton extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    final myCounter = Provider.of<MyCounter>(context, listen: false);
    print('MyIncreaseButton');
    return RaisedButton(
      child: Text('Increase +'),
      onPressed: ()=> myCounter.increment(),
    );

  }
}


class MyDecreaseButton extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    final myCounter = Provider.of<MyCounter>(context, listen: false);
    print('MyDecreaseButton');
    return RaisedButton(
      child: Text('Decrease -'),
      onPressed: ()=> myCounter.decrement(),
    );

  }
}

Now if I click on MyIncreaseButton widget, to inscrease the value, the MyDecreaseButton widget builds too, even when I dont click on it.

And vice versa, if I click on MyDecreaseButton widget, to descrease the value, the MyIncreaseButton widget builds too, even when I dont click on it.

My Expectation is:

When clicking MyIncreaseButton widget, MyDecreaseButton widget should not build.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Muhammad
  • 2,572
  • 4
  • 24
  • 46
  • Your example is unclear. Can you probide something that actually run? – Rémi Rousselet Jun 08 '19 at 10:59
  • @RémiRousselet, Hi and thank you, please have a look again to the question I have edited. As you can see there I have tree widgets and I want if update widget2 so only this widget should be build(render) not widget1 and widget3 if you have worked with `react-native` there is a lifecycle function called `shouldcomponentupdate(np, ns)` which let us avoid unnecessary rendering which are not belong to that. – Muhammad Jun 08 '19 at 11:57
  • ​ Why I am worry about a large list which each item of that is a widget so why I have asked this question: think my ListView has 1000 items, and I update only one item from the list, then here the entire items of the list gets rebuild. which rebuilding 1000 items on any update maybe decrease the application performance. – Muhammad Jun 08 '19 at 12:18
  • What do you mean by "if you update only widget2?" – Rémi Rousselet Jun 08 '19 at 12:32
  • I mean, lets me tell you what the problem. I have Chat App which I am using A ListView There every Item of my ListView is a `widget` So there is more than 2000 items when I scroll previus messages And then When I delete one of the message (I update only one widget) Here when I update one message(one widget) the entire List Items gets rebuild Here is my app responsiveness is slow after delete If I delete second message it works a very little slow – Muhammad Jun 08 '19 at 12:51
  • @RémiRousselet, please see the question again I have edited. I faced again with such problem in a large application. because there is more than 30 widgets, when I update one of them all 30 widgets gets rebuild. – Muhammad Aug 24 '19 at 19:56
  • Is `state build called` printed twice? – Rémi Rousselet Aug 25 '19 at 11:11
  • No, Just when I click on `MyIncreaseButton ` widget, the `MyDecreaseButton ` widget gets build too. Or when I click on `MyDecreaseButton ` widget, the `MyIncreaseButton ` widget gets build too. Here when one of them gets click the other should not build. – Muhammad Aug 25 '19 at 11:24
  • Why I am cocern about this, because here I just put the very simple of my code, I have more than 30 widgets, they are siblings or child and parent for each other. when I build one of them all 30 widgets gets rebuild. – Muhammad Aug 25 '19 at 11:26
  • With the code that you gave, that's not possible – Rémi Rousselet Aug 25 '19 at 11:54
  • Could please let me know, how to make it possible ? – Muhammad Aug 25 '19 at 12:02

3 Answers3

21

There are multiple solutions:

  • use context.read or pass listen: false to Provider.of:
RaisedButton(
  onPressed: () {
    context.read<MyModel>().increment();
    // alternatively, do:
    Provider.of<MyModel>(context, listen: false).increment();
  },
  child: Child(),
);
  • use context.select:
Widget build(context) {
  final increment = context.select((MyModel m) => m.increment);

  return RaisedButton(
    onPressed: increment,
    child: Child(),
  ); 
}
  • use Selector:
Widget build(context) {
  return Selector<MyModel, VoidCallback>(
    selector: (_, model) => model.increment,
    builder: (_, increment) {
      return RaisedButton(
        onPressed: increment,
        child: Child(),
      );
    },
  );
}
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 5
    thank you, I have edited as you mentioned, I passed `listen: false` but still on every click all three widgets rebuild. Please see the asnswer if I am still wrong. – Muhammad Aug 25 '19 at 11:08
  • 1
    I am facing the same issue.. In my case I have a Selector inside a StreamBuilder and When i call/change value which is marked by selected the whole widget tree rebuilds where i expect only the selector widget should rebuild. – sh0umik Jan 02 '20 at 14:55
  • 1
    @Muhammad How did you manage to resolve this, if at all? Thanks – JaffaKetchup Dec 24 '21 at 13:47
13

Selector is what you need. With selector you can filter updates. For example to update only when name change you can do something like this

Selector<AppStore, String>(
  selector: (_, store) => store.name,
  builder: (_, name, __) {
    return Text(name);
  },
);
Murat Erdogan
  • 798
  • 2
  • 10
  • 21
3

I just avoided unnecessary rendering by editing my code as the following:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/foundation.dart';

class MyCounter with ChangeNotifier {
  int _num = 0;

  int get num => _num;

  set num(int n) {
    _num = n;
    notifyListeners();
  }

  void increaent() {
    _num = _num + 1;
    notifyListeners();
  }

  void decrement() {
    _num = _num - 1;
    notifyListeners();
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('root build called');
    return ChangeNotifierProvider(
        builder: (context) => MyCounter(),
        child: MaterialApp(
          title: 'MyAppJan',
          home: Scaffold(
            appBar: AppBar(title: Text('Home')),
            body: AllWidget(),
          ),
          theme: ThemeData(primarySwatch: Colors.orange),
        ));
  }
}

class AllWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('state build called');
    return Center(
      child: Column(
        children: <Widget>[
          MyCounterText(),
          SizedBox(height: 10),
          MyIncreaseButton(),
          SizedBox(height: 10),
          MyDecreaseButton(),
        ],
        mainAxisAlignment: MainAxisAlignment.center,
        mainAxisSize: MainAxisSize.min,
      ),
    );
  }
}

class MyCounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('MyCounterText');
    return Consumer<MyCounter>(
      builder: (context, myCounter, _) {
        return Text(myCounter.num.toString());
      },
    );
  }
}

class MyIncreaseButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _items = Provider.of<MyCounter>(context,listen: false);
    print('MyIncreaseButton');
    return RaisedButton(
      child: Text('Increase ++'),
      onPressed: () => _items.increment(),
    );
  }
}

class MyDecreaseButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _items = Provider.of<MyCounter>(context,listen: false);
    print('MyDecreaseButton');
    return RaisedButton(
      child: Text('Decrease --'),
      onPressed: () => _items.decrement(),
    );
  }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Muhammad
  • 2,572
  • 4
  • 24
  • 46