0

ParentPage:

class ParentPage extends StatefulWidget {
  @override
  ParentPageState createState() => ParentPageState();
}

class ParentPageState<T extends ParentPage> extends State<T> {
  int counter = 0;

  void incrementCounter() => setState(() => counter++);

  @override
  Widget build(BuildContext context) => Text('$counter'); // Not updating
}

ChildPage:

class ChildPage extends ParentPage {
  @override
  _ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends ParentPageState<ChildPage> {
  @override
  Widget build(BuildContext context) {
    print('build[$counter]'); // Updates
    return Scaffold(
      body: ParentPage(),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}

I'm using home: ChildPage() in my MaterialApp widget.

Problem:

When I click on the FAB, it increments counter (which can be seen in the print statement of _ChildPageState.build method) but the Text widget in ParentPage stays at 0. Why is that so?

iDecode
  • 22,623
  • 19
  • 99
  • 186

3 Answers3

1

When building ChildPage you create a new instance of ParentPage.

body: ParentPage(),

That is a separate instance, so it has its own state.

It's just if you had for example 2 different Containers - you would not expect them to have the same properties just because they use the same class.

You can test it by checking the value of counter in the child widget.

class ChildPage extends ParentPage {
  @override
  _ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends ParentPageState<ChildPage> {
  @override
  Widget build(BuildContext context) {
    print('build[$counter]'); // Updates
    return Scaffold(
      body: Column(children: [ParentPage(), Text('$counter'),]),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}
  • Do you mean I'm creating multiple instances of `ParentPage`? – iDecode Sep 15 '21 at 15:21
  • @iDecode Yes, `body: ParentPage()` creates a second instance, with its own state. You could in theory replace it with `super.build(context)` but depending on what you actually want to do (other than the simple example) it may be better to extract a new Widget for the shared code, or use Provider for sharing more complex data. – Tomasz Zlamaniec Sep 16 '21 at 07:43
  • What creates the first instance if `body: ParentPage()` creates the second. – iDecode Sep 22 '21 at 19:34
  • @iDecode The first instance is created wherever ChildPage() is used by you, then ChildPage's build method calls constructor of ParentPage() creating the second independent widget. – Tomasz Zlamaniec Sep 24 '21 at 08:27
1

It stays at 0 because the call to setState() you are doing will update the state of _ChildPageState and not the state of your body: ParentPage() as it is a different instance.

Same goes for your print('build[$counter]'); it will display the correct value because it is the counter variable of your _ChildPageState and not the one from your ParentPage().

Edit:

_ChildPageState by extending your ParentPageState<ChildPage> has a counter variable and an incrementCounter() method.

The same goes for body: ParentPage(), as it is a new instance of ParentPage it has its own counter and incrementCounter().

By calling incrementCounter in your code like this:

class _ChildPageState extends ParentPageState<ChildPage> {
  @override
  Widget build(BuildContext context) {
    // ...

    return Scaffold(
      // ...
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter, // Here
        child: Icon(Icons.add),
      ),
    );
  }
}

You are refering to the incrementCounter method from your _ChildPageState to increment the counter value of _ChildPageState. This is why print('build[$counter]'); updates itself correctly as it is the counter from _ChildPageState.

Now for your body: ParentPage(), as I've said, it has its own properties and methods, which means that its method incrementCounter is never called and its counter value will never be incremented.

Example

Code

class ParentPage extends StatefulWidget {
  final String pageId;

  const ParentPage({required this.pageId});

  @override
  ParentPageState createState() => ParentPageState();
}

class ParentPageState<T extends ParentPage> extends State<T> {
  int counter = 0;

  void incrementCounter() {
    print('Update counter from: ${widget.pageId}');
    setState(() => counter++);
  }

  @override
  Widget build(BuildContext context) => Text('$counter'); // Not updating
}

class ChildPage extends ParentPage {
  const ChildPage() : super(pageId: 'ChildPage');

  @override
  _ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends ParentPageState<ChildPage> {
  @override
  Widget build(BuildContext context) {
    print('build[$counter]'); // Updates
    return Scaffold(
      body: const ParentPage(pageId: 'Body Page'),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Output

build[0]
Update counter from: ChildPage // First Tap
build[1]
Update counter from: ChildPage // Second Tap
build[2]

As you can see the incrementCounter from ParentPage(pageId: 'Body Page') is never called to increment its counter.

Try the example on DartPad

Guillaume Roux
  • 6,352
  • 1
  • 13
  • 38
  • But `setState` is used in the `ParentPage` and not in `ChildPage`. – iDecode Sep 15 '21 at 15:21
  • Not exactly, your instance of `_ChildPageState` is calling `setState` but your `body: ParentPage()` is a different instance so it has its own state with its own `counter` which is not updated by `incrementCounter` because this method is part of your instance of `_ChildPageState` . – Guillaume Roux Sep 15 '21 at 15:25
  • Sorry I still didn't get that. When I call `setState` in the `ParentPage`, it actually invokes the build of `ChildPage` and not the `ParentPage`? Can you also point two difference instances of `ParentPage`? Thanks – iDecode Sep 22 '21 at 19:38
  • I've updated my answer with a complete example to show that the counter from your `body: ParentPage()` doesn't update itself. – Guillaume Roux Sep 23 '21 at 10:03
  • Please use `@` while referring, I forgot to see your updated answer. Thank you – iDecode Oct 09 '21 at 19:37
0

Please try with custom widget

Parent page:

import 'package:flutter/material.dart';
class ParentPage extends StatefulWidget {
  @override
  ParentPageState createState() => ParentPageState();
}

class ParentPageState<T extends ParentPage> extends State<T> {
  int counter = 0;

  void incrementCounter() => setState(() => counter++);

  body() {
    return Center(child: Text('$counter'));
  }

  @override
  Widget build(BuildContext context) => body();

}

child page:

import 'package:flutter/material.dart';
import 'package:so_test/model/list_model.dart';
class ChildPage extends ParentPage {
  @override
  _ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends ParentPageState<ChildPage> {

  @override
  body() {
    return Scaffold(
      body: Center(child: Text("$counter"),),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}

So, this basically means that the BuildContext inside the build() method is actually that of it's parent. Hence, their is no need to explicitly pass it.

Jahidul Islam
  • 11,435
  • 3
  • 17
  • 38