Home > other >  Stateful widget inside an Provider (ChangeNotifier) widget does not get updated
Stateful widget inside an Provider (ChangeNotifier) widget does not get updated

Time:07-11

I have a Stateless-Provider widget along with its ChangeNotifier-model. Inside the Provider, there is a Stateful widget. When notifyListeners is called, all widgets in the stateless widget get updated, except the Stateful one. What am I missing here, and how do I go about it? Providing a simplified example here: Upon pressing the button, the expected result is First: The value is 1st, but the actual output is First: The value is 2nd

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

class Model extends ChangeNotifier {
  final List<ListElement> elements;
  Model({required this.elements});

  void add() {
    elements.insert(0, ListElement(name: "First", value: "1st"));
    notifyListeners();
  }
}


class ListElement {
  final String name;
  var value;
  ListElement({required this.name, required this.value});
}

class ValueWidget extends StatefulWidget {
  final String value;
  ValueWidget({required this.value});

  @override
  State<StatefulWidget> createState() => _ValueWidget(value: value);

}

class _ValueWidget extends State<ValueWidget> {

  String value;
  _ValueWidget({required this.value});

  @override
  Widget build(BuildContext context) {
    return Text("The value is ${value}.");
  }

}

class StatelessPage extends StatelessWidget {
  
  final model = Model(elements: [
    ListElement(name: "Second", value: "2nd"),
      ListElement(name: "Third", value: "3rd")]);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ChangeNotifierProvider(
        create: (context) => model,
        child: ConsumerWidget())
    );
  }
}

class ConsumerWidget extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Consumer<Model>(builder: (context, model, _) {
      return SingleChildScrollView(
        child: Container(
          padding: EdgeInsets.fromLTRB(10, 30, 10, 10000),
          child: Column(
            children: [Column(
                children: model.elements.map((element) {
                  return Row(children: [
                    Text("${element.name}: "),
                    ValueWidget(value: element.value)
                  ]);
                }).toList(),
            ),
              TextButton(onPressed: model.add,
                  child: Text("Add element to beginning")),
            ],
          ),
        ),
      );
    });
  }
}

Please consider that this is simplified version of my production code, and changing the whole Provider class to a Stateful one would be difficult.

Edit: Thanks Aimen for showing the path. What finally worked was using only the index of the list elements in the Stateful wiget (ValueWidget). And fetch the data from the model. I think the reason for this is that if the Stateful-widget in independece is not affected, it will not rebuild. We need to affect the build part of the widget. Pasting the changed working code.




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

class Model extends ChangeNotifier {
  final List<ListElement> elements;
  Model({required this.elements});

  void add() {
    elements.insert(0, ListElement(name: "First", value: "1st"));
    notifyListeners();
  }
}


class ListElement {
  final String name;
  var value;
  ListElement({required this.name, required this.value});
}

class ValueWidget extends StatefulWidget {
  final int ind;
  final Model model;
  ValueWidget({required this.ind, required this.model});

  @override
  State<StatefulWidget> createState() => _ValueWidget(
      ind: ind, model: model);
}

class _ValueWidget extends State<ValueWidget> {

  final int ind;
  final Model model;
  _ValueWidget({required this.ind, required this.model});

  @override
  Widget build(BuildContext context) {
    // Can also use Provider like this so that it does not need to be passed
    // final model = Provider.of<Model>(context, listen: true);
    // This is the part because of which widget is getting rebuilt
    final element = model.elements[ind];
    return Text("The value is ${element.value}.");
  }
}

class StatelessPage extends StatelessWidget {
  
  final model = Model(
      elements: [
    ListElement(name: "Second", value: "2nd"),
      ListElement(name: "Third", value: "3rd")]
  );
  
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: ChangeNotifierProvider(
        create: (context) => model,
        child: ConsumerWidget())
    );
  }
}

class ConsumerWidget extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Consumer<Model>(builder: (context, model, _) {
      return SingleChildScrollView(
        child: Container(
          padding: EdgeInsets.fromLTRB(10, 30, 10, 10000),
          child: Column(
            children: [Column(
                children:
                  model.elements.asMap().entries.map((ele) {
                  return Row(children: [
                    Text("${ele.value.name}: "),
                    ValueWidget(ind: ele.key, model: model),
                  ]);
                }).toList(),
            ),
              TextButton(onPressed: model.add,
                  child: Text("Add element to beginning")),
            ],
          ),
        ),
      );
    });
  }
}

CodePudding user response:

you are not implementing provider in the stateful widget you are just passing a value through a parameter you need to call a provider and set the listen to true inside the statful widget like

var model = Model.of(context, listen = true); List elements = model.elements;

here the elements variable will change when the elements in the provider will have a new value

  • Related