iDecode
iDecode

Reputation: 28886

Parent widget not updating using inheritance

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?

Upvotes: 0

Views: 90

Answers (3)

Guillaume Roux
Guillaume Roux

Reputation: 7298

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

Upvotes: 1

Jahidul Islam
Jahidul Islam

Reputation: 12565

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.

Upvotes: 0

Tomasz Zlamaniec
Tomasz Zlamaniec

Reputation: 17

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),
      ),
    );
  }
}

Upvotes: 1

Related Questions