FlutterFlow / flutterflow-issues

A community issue tracker for FlutterFlow.
97 stars 15 forks source link

postframe callbacks are being used in initState of generated code this is causing issues with state on initial build. #2820

Closed jonl-percsolutions-com closed 1 week ago

jonl-percsolutions-com commented 1 week ago

Has your issue been reported?

Current Behavior

Problem 1:

It appears all generated widget have a postframe callback to setState(). This causes in many cases, an unnecessary rebuild of the widget. Which can be a performance issue and undesired behavior.

Problem 2:

If you add a initialization action to a component or widget, it is also added to a postframeCallback. This means that the initialization action occurs AFTER the first build, requiring another rebuild for the proper state to be updated.

So, besides the performance penalty and flashing screens caused by unnecessary redrawing, there is a non-deterministic element to these updates as well, because one may use an async call back with or without an await and the set state callback is not awaited.

Depending on whether you thought it was silly to rebuild in an initialization action, this causes non-intuitive and hard to determine behavior in the application.

Expected Behavior

Everything generated in an initState,unless explicitly desired to complete after the fact, should complete by the time that the initState function is complete. using postFrameCallback, from my experience, in an initialization method, is a recipe for race conditions. IE, if say, the widget is accessed in postFrameCallback, the parent widget in a stateful widget is accessed in a postframe callback, it could be disposed by the time it's accessed due to a redraw or modified by the flutter framework due to intervening redraws.

Steps to Reproduce

  1. create new app
  2. create a new page or component
  3. add a component state variable to the page or component
  4. add an action to the top level page/component to set the initial state of the variable based upon a property of the page or component
  5. see non deterministic behavior or multiple redraws of the widget due ot this.

Reproducible from Blank

Bug Report Code (Required)

n/a

Context

I am trying to initialize a state variable that drives the visibility of a local widget, that is updated based upon a user interaction. This behavior is causing unintended side effects because of the race condition generated, like flashing redraws and unexpected state after a redraw.

Visual documentation

image

Generates the below. Rebuilding the widget should be unnecessary.


  @override
  void initState() {
    super.initState();
    _model = createModel(context, () => NotifcationPageModel());
    // On page load action.
    SchedulerBinding.instance.addPostFrameCallback((_) async {
      setState(() {
        FFAppState().currentPageRoute = FFAppConstants.notificationsPageRoute;
      });
      setDarkModeSetting(context, ThemeMode.light);
    });

    WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); ///why is this even here???  two draws of every widget due to init?
  }

The below would work just as well without forcing any redraws:

  @override
  void initState() {
    super.initState();
    _model = createModel(context, () => NotifcationPageModel());      
    //no need to redraw here at all, we are initializing the widget.
     FFAppState().currentPageRoute = FFAppConstants.notificationsPageRoute;
      setDarkModeSetting(context, ThemeMode.light);
}

///

Additional Info

No response

Environment

- FlutterFlow version: 4.1.45+, Desktop and Web
- Platform:All
- Browser name and version:Any
- Operating system and version affected: All

General

Relative to the time the changes were made, data was lost within

When following my steps to reproduce, data loss happens

jonl-percsolutions-com commented 1 week ago

Additional. I have a custom component that contains an optional header and a list. If there is a header, the list is collapsible, and is supposed to start collapsed. If there is no header, the list is shown initially as open. This is driven by a state variable.

The below is generated which causes the widget to flash across the screen, display correctly, then redraw multiple times incorrectly:

  void initState() {
    super.initState();
    _model = createModel(context, () => ProtocolItemListViewModel());

    // On component load action.
    SchedulerBinding.instance.addPostFrameCallback((_) async {
      _model.showList = widget.headerItem == null;
    });

    WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
  }
ignalauret commented 1 week ago

Hey @joni-percsolutions-com thanks for your report. I have confirmed that the generated code is not the ideal, and this could lead to some unexpected behaviour (as the ones you mentioned). I will send this to our technical team, they will review it and keep you updated on this thread.
Have a great day!

takashiff commented 1 week ago

Hi @jonl-percsolutions-com, thanks for the bug report! This is done intentionally since there are some cases where we can't immediately access some values in initState, thus requiring us to use a postframe callback instead. Hope this clears things up for you!

jonl-percsolutions-com commented 5 hours ago

Hi @jonl-percsolutions-com, thanks for the bug report! This is done intentionally since there are some cases where we can't immediately access some values in initState, thus requiring us to use a postframe callback instead. Hope this clears things up for you!

@takashiff , No this doesn't clear things up for me because, as mentioned in my ticket, it causes flickering bug in my UI. It would make sense, that the developer should be able to say whether or not the initState functions are async.

It also does not make sense for there to be an empty set state within the initState , that is also posted in a postframe callback. that forces a redraw. There are much better ways to handle it and it should only be added when necessary, if it is ever necessary because it is a surprise that it would do it at all.

A single postFramecallback that waits for access to a value would make possible sense, if it were conditionally added based upon the code generated, but the current set up make absolutely no sense whatsoever.