Find width of the child widget before build method is invoked again

330 views Asked by At

I am creating a custom button widget MyButton by composing ElevatedButton that will contains a Text widget as Child. It has a boolean isLoading property that will be used to configure the type of Button to build. When isLoading = true it will return a grey loading button having width same as the width of text provided prior to this loading button build. My question is how can I get the width of child Text widget before the build method is called or invoked again by setState, so that I can use that width to create the loading button of same size?

Loading Button will always be called after default Button has been built with child Text widget. (i.e. isLoading can be true only when widget is rebuilding not initially).

Here's the code:


class MyButton extends StatefulWidget {
  final String text;
  final bool isLoading;
  final VoidCallback onPressed;
  MyButton({Key key, @required this.text, @required this.isLoading, this.onPressed}) : super(key: key);
  @override
  State<StatefulWidget> createState() => _MyButtonState();
}

class _MyButtonState extends State<MyButton> {
  double _width; // Width of the [Text] child widget that will be used to get the [Container] of same width for loading button

  @override
  void initState() {
    super.initState();
    _width = 150;
  }

  @override
  void didUpdateWidget(covariant MyButton oldWidget) {
    super.didUpdateWidget(oldWidget);
    /*
    Calculate the width....
     */
  }

  @override
  Widget build(BuildContext context) {
    // I want to get the width of this child
    Widget child = Text(
      widget.text,
      style: TextStyle(color: Colors.black),
    );
    if (widget.isLoading) {
      child = Container(
        height: 60,
        width: _width, // this should be same as [Text] width
        color: Colors.grey,
      );
    }
    return ElevatedButton(
      child: child,
      onPressed: !widget.isLoading ? widget.onPressed : null,
      style: ButtonStyle(
        backgroundColor: MaterialStateProperty.all<Color>(
          !widget.isLoading ? Colors.red : Colors.grey,
        ),
      ),
    );
  }
}

Here is the demo of what I want to do but size of button when isLoading = true is absolute for now.

I have checked other posts and questions but could not find useful way to solve my usecase. Using GlobalKey is a solution but is not the recommended way and using RenderObject I was not able to solve this as in that case we can only get the intrinsic heights and widths of the parent.

I think there might be some other way to do this other than using GlobalKey and RenderObject.

2

There are 2 answers

2
Diwyansh On

Rather than creating a Container for that I would prefer you just hide the text so that size of button will be same as previous. Try below code :

Widget build(BuildContext context) {
    return ElevatedButton(
      child: Opacity(
        opacity: !widget.isLoading ? 1 : 0,
        child: Text(
          widget.text,
          style: TextStyle(color: Colors.black),
        ),
      ),
      onPressed: !widget.isLoading ? widget.onPressed : null,
      style: ButtonStyle(
        backgroundColor: MaterialStateProperty.all<Color>(
          !widget.isLoading ? Colors.red : Colors.grey,
        ),
      ),
    );
  }
0
Adarsh Srivastava On

We can use the add postFrameCallback to WidgetBinding instance to get the size of the widget from the context inside postFrameCallback.

NOTE: This may not be the efficient appraoch.

Here's the code that worked in my case.


  @override
  void initState() {
    super.initState();
    getWidth();
    _width = _width ?? 150;
  }

  void getWidth() {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      if (context?.size is Size) {
        _width = context.size.width;
        print(context.size.toString());
      }
    });
  }

  @override
  void didUpdateWidget(covariant MyButton oldWidget) {
    getWidth();
    super.didUpdateWidget(oldWidget);
  }