diff --git a/docs/generated-code/component-gen-code.md b/docs/generated-code/component-gen-code.md new file mode 100644 index 00000000..6aa23223 --- /dev/null +++ b/docs/generated-code/component-gen-code.md @@ -0,0 +1,39 @@ +--- +title: Components +slug: /generated-code/component-model +sidebar_position: 6 +--- + +# Generated Code: Components + +Similar to a [**Page**](pages-generated-code.md), when creating a **[component](../resources/ui/components/intro-components.md)** in FlutterFlow, it automatically generates two files: a `Widget` class and a `Model` class. + +:::info[Prerequisites] +This guide uses examples from the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v1)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. +::: + +## ComponentModel class + +`ComponentModel` classes are responsible for managing the state and behavior of individual components used within a page. These classes extend the `FlutterFlowModel` class, providing a consistent structure and shared functionality across all component models. This ensures that each component's state is isolated and reusable, making the app easier to maintain and scale. + +The lifecycle of a `ComponentModel` and its associated widget class follows the same structure as a page. For more details, refer to the documentation on **[Generated Pages](pages-generated-code.md)**. + +### onComponentLoad Action: Generated Code + +When you define actions for the `onComponentLoad` action trigger of a component, these actions are added inside an `addPostFrameCallback` method within the page's `initState` method. This ensures that the actions are executed only after the initial widget tree is built. + +```js + @override + void initState() { + super.initState(); + _model = createModel(context, () => ProductListPageModel()); + + // On component load action. + SchedulerBinding.instance.addPostFrameCallback((_) async { + await _model.updateTotalCost(context); + safeSetState(() {}); + }); + + } +``` + diff --git a/docs/generated-code/flutterflow-model.md b/docs/generated-code/flutterflow-model.md new file mode 100644 index 00000000..de0b875d --- /dev/null +++ b/docs/generated-code/flutterflow-model.md @@ -0,0 +1,109 @@ +--- +title: FlutterFlow Model +slug: /generated-code/flutterflow-model +sidebar_position: 4 +--- + +# FlutterFlow Model + +The `FlutterFlowModel` class is an abstract class used in FlutterFlow to provide a unified and extensible structure for managing state and behavior of widgets (both pages and components). It encapsulates **initialization, state management,** and **disposal** logic, making it easier to handle the lifecycle of widgets and their models. + +FlutterFlow automatically generates the `flutter_flow_model.dart` file, which contains the `FlutterFlowModel` class and utility methods like `wrapWithModel()` and `createModel()`. + +The diagram below illustrates how these utility classes and methods are utilized in a widget or model class: + + +![page-generated.png](imgs/page-generated.png) + +When a component is added to your page (and every component you create [generates both a widget and a model class)](component-gen-code.md), the flow below explains how the utility classes are used when there is a child component: + +![page-component-generated.png](imgs/page-component-generated.png) + +

+ +Here’s a breakdown of the lifecycle of `FlutterFlowModel` class: + +## Initialization +Ensures the model is initialized **only once** and is tied to the `BuildContext` and the widget it is associated with. + +```js +abstract class FlutterFlowModel { + // Initialization methods + bool _isInitialized = false; + void initState(BuildContext context); + void _init(BuildContext context) { + if (!_isInitialized) { + initState(context); + _isInitialized = true; + } + if (context.widget is W) _widget = context.widget as W; + _context = context; + } +``` + + +## Widget & Context references + +Provides references to the associated widget and its `BuildContext`. + +```js + // The widget associated with this model. This is useful for accessing the + // parameters of the widget, for example. + W? _widget; + W? get widget => _widget; + + // The context associated with this model. + BuildContext? _context; + BuildContext? get context => _context; +``` + +`_widget` and `_context` (private fields) store the widget and context references. `widget` and `context` (getters) are the public accessors for `_widget` and `_context`. + +## Disposal + +Manages the cleanup of resources when the model or widget is disposed. + +```js + bool disposeOnWidgetDisposal = true; + void dispose(); + void maybeDispose() { + if (disposeOnWidgetDisposal) { + dispose(); + } + // Remove reference to widget for garbage collection purposes. + _widget = null; + } +``` +The `disposeOnWidgetDisposal` determines whether the model should be disposed when the widget is removed. This defaults to `true` for **pages** and `false` for **components** (as parent models typically manage their child components). + +The `maybeDispose()` checks `disposeOnWidgetDisposal` before disposing. It removes the widget reference to aid garbage collection. + +## Updates and Change Notification + +Allows the model to notify the associated widget or parent component/page when updates occur. + +```js + // Whether to update the containing page / component on updates. + bool updateOnChange = false; + // Function to call when the model receives an update. + VoidCallback _updateCallback = () {}; + void onUpdate() => updateOnChange ? _updateCallback() : () {}; + + FlutterFlowModel setOnUpdate({ + bool updateOnChange = false, + required VoidCallback onUpdate, + }) => + this + .._updateCallback = onUpdate + ..updateOnChange = updateOnChange; + + // Update the containing page when this model received an update. + void updatePage(VoidCallback callback) { + callback(); + _updateCallback(); + } +``` + +## wrapWithModel() + +The `wrapWithModel()` method in FlutterFlow links a model to a widget and its child widgets, allowing them to access and manage state. It wraps the widget with a Provider, making the model available throughout the widget tree. \ No newline at end of file diff --git a/docs/generated-code/imgs/page-component-generated.png b/docs/generated-code/imgs/page-component-generated.png new file mode 100644 index 00000000..89f38d1d Binary files /dev/null and b/docs/generated-code/imgs/page-component-generated.png differ diff --git a/docs/generated-code/imgs/page-generated.png b/docs/generated-code/imgs/page-generated.png new file mode 100644 index 00000000..c0ef3f8f Binary files /dev/null and b/docs/generated-code/imgs/page-generated.png differ diff --git a/docs/generated-code/imgs/page-generation-initial.png b/docs/generated-code/imgs/page-generation-initial.png new file mode 100644 index 00000000..668ed040 Binary files /dev/null and b/docs/generated-code/imgs/page-generation-initial.png differ diff --git a/docs/generated-code/page-model.md b/docs/generated-code/page-model.md deleted file mode 100644 index 9983cb48..00000000 --- a/docs/generated-code/page-model.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: PageModel class -sidebar_position: 4 ---- -# PageModel class - -:::info[Prerequisites] -This guide uses example of the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v1)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. -::: - - -When you create a new page in FlutterFlow, it automatically generates two files: a `Widget` class and a `Model` class. The `PageModel` classes are responsible for managing the state of individual pages and initializing the components used in these Pages. These classes extend the `FlutterFlowModel` class, which provides a consistent structure and shared functionality across all page models. - -A `PageModel` class typically holds local state fields specific to the page, which correspond to the **[Page State variables](../resources/ui/pages/page-lifecycle.md#page-state)**. For example, in the `product_list_page_model.dart` [file](https://github.com/FlutterFlow/sample-apps/blob/main/ecommerce_flow/lib/product/product_list_page/product_list_page_model.dart) (which is the `Model` file for the `ProductListPage`), one of the state fields might be `_searchString`. This private field stores the current search string and includes a getter and setter to manage its value while logging any changes. - -```js -String? _searchString; -set searchString(String? value) { - _searchString = value; - debugLogWidgetClass(rootModel); -} -String? get searchString => _searchString; -``` - -In addition to managing local state, the `PageModel` class also contains fields for handling the state of widgets on the page. For instance, `_dropDownValue` is a private field that stores the current value of a dropdown widget. Similar to `_searchString`, it has a getter and setter that logs changes to this field. - -```js -String? _dropDownValue; -set dropDownValue(String? value) { - _dropDownValue = value; - debugLogWidgetClass(rootModel); -} -String? get dropDownValue => _dropDownValue; -``` - -The `PageModel` class is also responsible for initializing the models of components used on the page. For example, if the page includes a `CartCounter` component, the model for this component is initialized within the page's model class. - -```js -// Model for CartCounter component. - late CartCounterModel cartCounterModel; - -@override -void initState(BuildContext context) { - cartCounterModel = createModel(context, () => CartCounterModel()..parentModel = this); - -} -``` - -When dealing with dynamic lists of components, such as those in a `ListView`, Row, or Column widget, the `PageModel` initializes a `Map` to manage the state of each component instance. For example, if the page includes a list of `CategoryAvatar` components, the initialization might look like this: - -```js -// Models for CategoryAvatar dynamic component. - Map categoryAvatarModels = {}; - -``` - -Finally, the `dispose` function in the `ProductListPageModel` class is used to clean up resources when they are no longer needed. This is a common practice in Flutter to prevent memory leaks. In this class, the `dispose` function is overridden to dispose of the `cartCounterModel`, `searchQueryFocusNode`, and `searchQueryTextController`. - -```js - - @override - void dispose() { - cartCounterModel.dispose(); - searchQueryFocusNode?.dispose(); - searchQueryTextController?.dispose(); - } -``` \ No newline at end of file diff --git a/docs/generated-code/pages-generated-code.md b/docs/generated-code/pages-generated-code.md new file mode 100644 index 00000000..237fa881 --- /dev/null +++ b/docs/generated-code/pages-generated-code.md @@ -0,0 +1,183 @@ +--- +title: Pages +slug: /generated-code/page-model +sidebar_position: 5 +--- + +# Generated Code: Pages + +When you create a new Page in FlutterFlow, it automatically generates two files: a `Widget` class and a `Model` class. So if the name of the page you created is called **ProductListPage**, FlutterFlow generation backend will automatically create **ProductListPageWidget** class and **ProductListPageModel** class. + +:::info[Prerequisites] +This guide uses examples from the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v1)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. +::: + +## PageModel class + + The `PageModel` classes are responsible for managing the state of individual pages and initializing the components used in these Pages. These classes extend the `FlutterFlowModel` class, which provides a consistent structure and shared functionality across all page models. + +The following diagram shows how FlutterFlow generates the model and widget class when you create a new Page in FlutterFlow: +![page-generation-initial.png](imgs/page-generation-initial.png) + +:::tip[FlutterFlow Model] +To learn more about the utility classes and methods that FlutterFlow generates for all pages & components, see [**the FlutterFlowModel document**](flutterflow-model.md). +::: + + + +#### Managing Local State + +A `PageModel` class typically holds local state fields specific to the page, which correspond to the **[Page State variables](../resources/ui/pages/page-lifecycle.md#page-state)**. + +For example, in the ProductListPage, user may create a Page State variable called `searchString`. Correspondingly, in the `product_list_page_model.dart` [file](https://github.com/FlutterFlow/sample-apps/blob/main/ecommerce_flow/lib/product/product_list_page/product_list_page_model.dart) (which is the `Model` file for the `ProductListPage`), the corresponding state field would be `_searchString`. This private field stores the current search string and includes a getter and setter to manage its value while logging any changes. + +```js +String? _searchString; +set searchString(String? value) { + _searchString = value; + debugLogWidgetClass(rootModel); +} +String? get searchString => _searchString; +``` + +:::tip[Private variables in Dart] +In Dart, variables that start with an underscore (`_`), such as `_searchString`, are private to the class. This means they cannot be accessed outside the class or its scope. +::: + +In addition to managing local state, the given `PageModel` class also contains fields for handling the state of widgets on the page. For instance, `_dropDownValue` is a private field that stores the current value of a dropdown widget (if it is added to the current Page). Similar to `_searchString`, it has a getter and setter that logs changes to this field. + +```js +String? _dropDownValue; +set dropDownValue(String? value) { + _dropDownValue = value; + debugLogWidgetClass(rootModel); +} +String? get dropDownValue => _dropDownValue; +``` + +#### Initializing child component models +The `PageModel` class is also responsible for initializing the models of components used on the page. For example, if the page includes a `CartCounter` component, the model for this component is initialized within the page's model class. + +```js +// Model for CartCounter component. + late CartCounterModel cartCounterModel; + +@override +void initState(BuildContext context) { + cartCounterModel = createModel(context, () => CartCounterModel()..parentModel = this); + +} +``` +:::info +Only the model class of a child component is initialized inside the page or parent model class. In the case of page model classes, they are initialized within the widget’s state class itself. See the **[Widget class section](#pagewidget-class)** for more details. +::: + +When dealing with dynamic lists of components, such as those in a `ListView`, Row, or Column widget, the `PageModel` initializes a `Map` to manage the state of each component instance. For example, if the page includes a list of `CategoryAvatar` components, the initialization might look like this: + +```js +// Models for CategoryAvatar dynamic component. + Map categoryAvatarModels = {}; +``` + +#### dispose() + +Finally, the `dispose` function in the `ProductListPageModel` class is used to clean up resources when they are no longer needed. This is a common practice in Flutter to prevent memory leaks. In this class, the `dispose` function is overridden to dispose of the `cartCounterModel`, `searchQueryFocusNode`, and `searchQueryTextController`. + +```js + + @override + void dispose() { + cartCounterModel.dispose(); + searchQueryFocusNode?.dispose(); + searchQueryTextController?.dispose(); + } +``` + + +## PageWidget class + +The `PageWidget` classes are responsible for creating the UI of individual pages and holding the widget tree as designed in the FlutterFlow canvas. These classes always extend Flutter's `StatefulWidget` class utilizing Flutter's built-in state management through `setState` to handle dynamic updates and interact with the app's lifecycle. + +```js +class ProductListPageWidget extends StatefulWidget { + const ProductListPageWidget({super.key}); + + @override + State createState() => _ProductListPageWidgetState(); +} +``` + +#### PageModel Initialization +Within the State class, the `PageModel` object is initialized. [This class](#pagemodel-class) serves as a centralized place to manage the page’s state, handle business logic, and interact with the data layer. + +```js +class _ProductListPageWidgetState extends State { + late ProductListPageModel _model; + + @override + void initState() { + super.initState(); + _model = createModel(context, () => ProductDetailPageModel()); + + } +``` + +#### PageModel Dispose +Similarly, the [`dispose` method](#dispose) of the `PageModel` class is invoked from the **overridden** `dispose` method of the widget's **State** class. This ensures that any resources managed by the `PageModel`, such as listeners or controllers, are properly released when the widget is removed from the widget tree. + +```js + @override + void dispose() { + _model.dispose(); + super.dispose(); + } +``` + +#### Global Scaffold Key +Each page includes a `GlobalKey` for the `Scaffold`, which can be used to manage the scaffold's state, such as opening or closing drawers or snackbars programmatically. + +```js +final scaffoldKey = GlobalKey(); + +return Scaffold( + key: scaffoldKey, + ...) +``` + +#### Keyboard Dismissal +Moreover, the root widget of every page is a `GestureDetector` with an `onTap` callback that unfocuses the current input field. This approach ensures that tapping anywhere outside an input field dismisses the keyboard or removes focus, creating a better user experience. + +```js +return GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + FocusManager.instance.primaryFocus?.unfocus(); + }, +...) +``` + +These functionalities are automatically added by FlutterFlow to ensure seamless navigation and proper keyboard handling across pages. + +### onPageLoad Action: Generated Code + +When you define actions for the `onPageLoad` action trigger of a Page, these actions are added inside an `addPostFrameCallback` method within the page's `initState` method. This ensures that the **on Page Load** actions are executed after the widget is fully built and rendered. This avoids issues caused by trying to update the UI before it is ready. + +```js + @override + void initState() { + super.initState(); + _model = createModel(context, () => ProductListPageModel()); + + // On page load action. + SchedulerBinding.instance.addPostFrameCallback((_) async { + _model.searchString = null; + safeSetState(() {}); + ... // more actions + }); + + } +``` + +:::tip[safe Set State] +The `safeSetState` method is a custom implementation built on top of Flutter's `setState` method. It ensures that `setState` is only called when the widget is currently mounted, preventing potential runtime errors. +::: \ No newline at end of file diff --git a/docs/resources/ui/pages/intro-pages.md b/docs/resources/ui/pages/intro-pages.md index 4b47a7bf..704a6457 100644 --- a/docs/resources/ui/pages/intro-pages.md +++ b/docs/resources/ui/pages/intro-pages.md @@ -28,11 +28,11 @@ Whether you're starting from scratch, using a template, or leveraging AI tools, there are several pathways to achieve the desired functionality and aesthetic of your desired Page. :::tip[Generated Code] -When you create a page in FlutterFlow, a `Widget` class and a corresponding `Model` class are automatically generated. You can view these in the Code Viewer. To explore the details of the generated `Model` class, take a closer [**look at the code**](../../../generated-code/page-model.md). +When you create a page in FlutterFlow, a `Widget` class and a corresponding `Model` class are automatically generated. You can view these in the Code Viewer. To explore the details of the generated `Model` class, take a closer [**look at the code**](../../../generated-code/pages-generated-code.md). ::: FlutterFlow allows you to easily create new pages using the **Add Page, Component, or Flow** button, -which is available from the **Page Selector** tab in the **Navgation Menu**. This will help you +which is available from the **Page Selector** tab in the **Navigation Menu**. This will help you quickly start and add new pages to your app. diff --git a/docs/resources/ui/pages/page-lifecycle.md b/docs/resources/ui/pages/page-lifecycle.md index aef73651..de13aef0 100644 --- a/docs/resources/ui/pages/page-lifecycle.md +++ b/docs/resources/ui/pages/page-lifecycle.md @@ -74,6 +74,10 @@ To add an action to **On Page Load** action trigger, follow the steps: +:::tip[Generated Code] +When you add actions to the **on Page Load** action trigger, they are executed within a `SchedulerBinding.instance.addPostFrameCallback((_)` method. This ensures that the actions run after the widget tree is fully built. For more details, refer to the [**Page: Generated Code**](../../../generated-code/pages-generated-code.md#onpageload-action-generated-code) document. +::: + ### On Phone Shake [Action Trigger] Actions added under this trigger are triggered when the