import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; Widget wrapWithModel({ required T model, required Widget child, required VoidCallback updateCallback, bool updateOnChange = false, }) { // Set the component to optionally update the page on updates. model.setOnUpdate( onUpdate: updateCallback, updateOnChange: updateOnChange, ); // Models for components within a page will be disposed by the page's model, // so we don't want the component widget to dispose them until the page is // itself disposed. model.disposeOnWidgetDisposal = false; // Wrap in a Provider so that the model can be accessed by the component. return Provider.value( value: model, child: child, ); } T createModel( BuildContext context, T Function() defaultBuilder, ) { final model = context.read() ?? defaultBuilder(); model._init(context); return model; } 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; } // The widget associated with this model. This is useful for accessing the // parameters of the widget, for example. W? _widget; W? get widget => _widget; // Dispose methods // Whether to dispose this model when the corresponding widget is // disposed. By default this is true for pages and false for components, // as page/component models handle the disposal of their children. bool disposeOnWidgetDisposal = true; void dispose(); void maybeDispose() { if (disposeOnWidgetDisposal) { dispose(); } // Remove reference to widget for garbage collection purposes. _widget = null; } // 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(); } } class FlutterFlowDynamicModels { FlutterFlowDynamicModels(this.defaultBuilder); final T Function() defaultBuilder; final Map _childrenModels = {}; final Map _childrenIndexes = {}; Set? _activeKeys; T getModel(String uniqueKey, int index) { _updateActiveKeys(uniqueKey); _childrenIndexes[uniqueKey] = index; return _childrenModels[uniqueKey] ??= defaultBuilder(); } List getValues(S? Function(T) getValue) { return _childrenIndexes.entries // Sort keys by index. .sorted((a, b) => a.value.compareTo(b.value)) .where((e) => _childrenModels[e.key] != null) // Map each model to the desired value and return as list. In order // to preserve index order, rather than removing null values we provide // default values (for types with reasonable defaults). .map((e) => getValue(_childrenModels[e.key]!) ?? _getDefaultValue()!) .toList(); } S? getValueAtIndex(int index, S? Function(T) getValue) { final uniqueKey = _childrenIndexes.entries.firstWhereOrNull((e) => e.value == index)?.key; return getValueForKey(uniqueKey, getValue); } S? getValueForKey(String? uniqueKey, S? Function(T) getValue) { final model = _childrenModels[uniqueKey]; return model != null ? getValue(model) : null; } void dispose() => _childrenModels.values.forEach((model) => model.dispose()); void _updateActiveKeys(String uniqueKey) { final shouldResetActiveKeys = _activeKeys == null; _activeKeys ??= {}; _activeKeys!.add(uniqueKey); if (shouldResetActiveKeys) { // Add a post-frame callback to remove and dispose of unused models after // we're done building, then reset `_activeKeys` to null so we know to do // this again next build. SchedulerBinding.instance.addPostFrameCallback((_) { _childrenIndexes.removeWhere((k, _) => !_activeKeys!.contains(k)); _childrenModels.keys .toSet() .difference(_activeKeys!) // Remove and dispose of unused models since they are not being used // elsewhere and would not otherwise be disposed. .forEach((k) => _childrenModels.remove(k)?.maybeDispose()); _activeKeys = null; }); } } } T? _getDefaultValue() { switch (T) { case int: return 0 as T; case double: return 0.0 as T; case String: return '' as T; case bool: return false as T; default: return null as T; } } extension TextValidationExtensions on String? Function(BuildContext, String?)? { String? Function(String?)? asValidator(BuildContext context) => this != null ? (val) => this!(context, val) : null; }