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