Penumbra
Penumbra

Reputation: 195

Flutter - How to Extract Widget with onPressed setState inside?

I want to Extract a Widget with onPressed setState inside but I get the Message "Reference to an enclosing class method cannot be extracted." Is there a way to do that?

I would like to divide my code into different widgets so that it remains clear. Here is simplified an example of the code:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Calculator(),
    );
  }
}

class Calculator extends StatefulWidget {
  @override
  _CalculatorState createState() => _CalculatorState();
}

class _CalculatorState extends State<Calculator> {
  var myValue = 0;

  void calculate() {
    myValue = 12;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Container(
            child: TextButton(
              onPressed: () {
                setState(() {
                  calculate();
                });
              },
              child: Text(
                'Button 001',
              ),
            ),
          ),
          TextOutput(myValue: myValue),
        ],
      ),
    );
  }
}

class TextOutput extends StatelessWidget {
  const TextOutput({
    Key key,
    @required this.myValue,
  }) : super(key: key);

  final int myValue;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(
        myValue.toString(),
      ),
    );
  }
}

The part I want to extract into a separate widget:

  Container(
    child: TextButton(
      onPressed: () {
        setState(() {
          calculate();
        });
      },
      child: Text(
        'Button 001',
      ),
    ),
  ),

Upvotes: 4

Views: 3416

Answers (4)

in flutter: extracting a widget

"A reference to an enclosing class method can't be extracted," ERROR

this happens when the widget we are trying to extract uses setState

(which is specific to statefull widgets)

and it means = NO ACCESS TO setState method

HOW TO FIX THIS?

  1. Extract as a function
  2. Extract as a widget a-Extract as a stateless widget b-Extract as a statefull widget:

b-1: Extract with a callBack: The widget is stateful, but it communicates state changes to its parent via the callback,this maintains a single source of truth for the state.

b-2: Extract without a callBack in this case every instance will have its own internal state(isolation). which leads to a difficulty for parent widget to control or access the state.

Mostly we either extract as a function, as a stateless widget, or as a statefull widget with a callback.

when to extract as a statefull widget without a callBack?

  • if the widget is self-contained and doesnt need to effect other parts of your app.
  • if it is a widget with independent animations or temporary UI changes thet doesn't need to be synchronized with other widgets.

Upvotes: 0

FadyFouad
FadyFouad

Reputation: 948

Flutter offers VoidCallback and Function(x) (where x can be a different type) for callback-style events between child and parent widgets.

Simply You can pass Function onPressed; via constructor Here is your Extracted Container widget:

class ExtractedContainer extends StatelessWidget {
  final Function onPressed;
  const ExtractedContainer({
    Key key, @required this.onPressed,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      child: TextButton(
        onPressed: () {
          onPressed();
        },
        child: Text(
          'Button 001',
        ),
      ),
    );
  }
}

And Here How to use it:

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ExtractedContainer(onPressed: calculate,),
          TextOutput(myValue: myValue),
        ],
      ),
    );
  }

Your full code example

import 'package:flutter/material.dart';

class MyApp2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Calculator(),
    );
  }
}

class Calculator extends StatefulWidget {
  @override
  _CalculatorState createState() => _CalculatorState();
}

class _CalculatorState extends State<Calculator> {
  var myValue = 0;

  void calculate() {
    myValue = 12;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ExtractedContainer(onPressed: calculate,),
          TextOutput(myValue: myValue),
        ],
      ),
    );
  }
}

class ExtractedContainer extends StatelessWidget {
  final Function onPressed;
  const ExtractedContainer({
    Key key, @required this.onPressed,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      child: TextButton(
        onPressed: () {
          onPressed();
        },
        child: Text(
          'Button 001',
        ),
      ),
    );
  }
}

class TextOutput extends StatelessWidget {
  const TextOutput({
    Key key,
    @required this.myValue,
  }) : super(key: key);

  final int myValue;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(
        myValue.toString(),
      ),
    );
  }
}

Upvotes: 6

Md. Yeasin Sheikh
Md. Yeasin Sheikh

Reputation: 63584

You can use VoidCallback on extract widget to get onPressed event

class MyContainer extends StatelessWidget {
  final VoidCallback onTap;
  const MyContainer({
    Key? key,
    required this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TextButton(
        onPressed: onTap,
        child: Text(
          'Button 001',
        ),
      ),
    );
  }
}

And use like

          MyContainer(
            onTap: () {
              print("tapped");

              setState(() {
                calculate();
              });
            },
          ),

Upvotes: 0

Huthaifa Muayyad
Huthaifa Muayyad

Reputation: 12353

Setstate is related to the widget you want to refresh its state. If you extract it to another place, then setState refers to the state of the new widget.

In your case, the setState will only change the state of the container encapsulating your widget which you are trying to extract and its children, it doesn't migrate upward.

Unless, you look for the state of the widget you want, using exact type, and then trigger the state there, but this is overkill, a lot harder, requires more code, than what you currently have.

Upvotes: 0

Related Questions