Alex
Alex

Reputation: 1957

Flutter setState not updating child element

I have an InkWell which uses onTap to perform some actions. When the button is tapped, I like an indicator to be shown (in case the action is long-running). However, the setState in the InkWell does not trigger its children to be re-rendered. The code is as follows:

class PrimaryButtonState extends State<PrimaryButton> {

bool _apiCall;

Widget getWidget() {
  if(_apiCall) {
    return new CircularProgressIndicator();
  } else {
    return Text(
      widget.label,
    );
  }
}

@override
Widget build(BuildContext context) {
  final List<Color> colors = //omitted

  return InkWell(
    child: Container(
      decoration: // omitted
      child: getWidget(), // not updated when _apiCall changes !!!!!
    ),
    onTap: () {
      setState(() {
        _apiCall = true;
      });
      widget.onTab(context);
      setState(() {
        _apiCall = false;
      });
    }
  );
}
}

How can I solve this that getWidget returns the correct widget dependent on _apiCall?


EDIT:

The widget.onTap contains the following:

void performLogin(BuildContext context) {
final String userName = _userName.text.trim();
final String password = _password.text.trim();

UserService.get().loginUser(userName, password).then((val) {
  Navigator.push(
      context, MaterialPageRoute(builder: (context) => MainLayout()));
}).catchError((e) {
  // omitted
});

}

it is passed with the widget:

class PrimaryButton extends StatefulWidget {

  final bool isPrimary;
  final String label;
  final Function(BuildContext context) onTab;

  PrimaryButton(this.label, this.isPrimary, this.onTab);

  @override
  State<StatefulWidget> createState() => PrimaryButtonState();

}

My main concern is, that the given onTap method should not know it is "bound" to a UI widget and therefore should not setState. Also, as this is a general button implementation I like it to be interchangeable (therefore, onTap is not hardcoded)

Upvotes: 0

Views: 919

Answers (1)

Jwildsmith
Jwildsmith

Reputation: 1035

It looks like your problem is because you are calling setState() twice in your onTap() function. Since onTap() is not an async function it will set _apiCall = true in the first setState, then immediately run widget.onTab(context) and then immediately perform the second setState() to set _apiCall = false so you never see the loading widget.

To fix this you will need to make your onTab function an async function and await for a value in your onTap function for your InkWell:

onTap: () async {
      setState(() {
        _apiCall = true;
      });
      await widget.onTab(context);
      setState(() {
        _apiCall = false;
      });
    }

This will also let you use the results of your onTab function to show errors or other functionality if needed.

If you are unsure how to use async functions and futures here is a good guide on it that goes over this exact kind of use case.

Upvotes: 1

Related Questions