// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Template: dev/snippets/config/templates/stateful_widget_scaffold_center.tmpl // // Comment lines marked with "▼▼▼" and "▲▲▲" are used for authoring // of samples, and may be ignored if you are just exploring the sample. // Flutter code sample for Actions // //*************************************************************************** //* ▼▼▼▼▼▼▼▼ description ▼▼▼▼▼▼▼▼ (do not modify or remove section marker) // This example creates a custom [Action] subclass `ModifyAction` for modifying // a model, and another, `SaveAction` for saving it. // // This example demonstrates passing arguments to the [Intent] to be carried to // the [Action]. Actions can get data either from their own construction (like // the `model` in this example), or from the intent passed to them when invoked // (like the increment `amount` in this example). // // This example also demonstrates how to use Intents to limit a widget's // dependencies on its surroundings. The `SaveButton` widget defined in this // example can invoke actions defined in its ancestor widgets, which can be // customized to match the part of the widget tree that it is in. It doesn't // need to know about the `SaveAction` class, only the `SaveIntent`, and it // only needs to know about a value notifier, not the entire model. //* ▲▲▲▲▲▲▲▲ description ▲▲▲▲▲▲▲▲ (do not modify or remove section marker) //*************************************************************************** import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// This is the main application widget. class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); static const String _title = 'Flutter Code Sample'; @override Widget build(BuildContext context) { return MaterialApp( title: _title, home: Scaffold( appBar: AppBar(title: const Text(_title)), body: const Center( child: MyStatefulWidget(), ), ), ); } } //***************************************************************************** //* ▼▼▼▼▼▼▼▼ code-preamble ▼▼▼▼▼▼▼▼ (do not modify or remove section marker) // A simple model class that notifies listeners when it changes. class Model { ValueNotifier isDirty = ValueNotifier(false); ValueNotifier data = ValueNotifier(0); int save() { if (isDirty.value) { print('Saved Data: ${data.value}'); isDirty.value = false; } return data.value; } void setValue(int newValue) { isDirty.value = data.value != newValue; data.value = newValue; } } class ModifyIntent extends Intent { const ModifyIntent(this.value); final int value; } // An Action that modifies the model by setting it to the value that it gets // from the Intent passed to it when invoked. class ModifyAction extends Action { ModifyAction(this.model); final Model model; @override void invoke(covariant ModifyIntent intent) { model.setValue(intent.value); } } // An intent for saving data. class SaveIntent extends Intent { const SaveIntent(); } // An Action that saves the data in the model it is created with. class SaveAction extends Action { SaveAction(this.model); final Model model; @override int invoke(covariant SaveIntent intent) => model.save(); } class SaveButton extends StatefulWidget { const SaveButton(this.valueNotifier, {Key? key}) : super(key: key); final ValueNotifier valueNotifier; @override State createState() => _SaveButtonState(); } class _SaveButtonState extends State { int savedValue = 0; @override Widget build(BuildContext context) { return AnimatedBuilder( animation: widget.valueNotifier, builder: (BuildContext context, Widget? child) { return TextButton.icon( icon: const Icon(Icons.save), label: Text('$savedValue'), style: ButtonStyle( foregroundColor: MaterialStateProperty.all( widget.valueNotifier.value ? Colors.red : Colors.green, ), ), onPressed: () { setState(() { savedValue = Actions.invoke(context, const SaveIntent())! as int; }); }, ); }, ); } } //* ▲▲▲▲▲▲▲▲ code-preamble ▲▲▲▲▲▲▲▲ (do not modify or remove section marker) //***************************************************************************** /// This is the stateful widget that the main application instantiates. class MyStatefulWidget extends StatefulWidget { const MyStatefulWidget({Key? key}) : super(key: key); @override State createState() => _MyStatefulWidgetState(); } /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { //******************************************************************** //* ▼▼▼▼▼▼▼▼ code ▼▼▼▼▼▼▼▼ (do not modify or remove section marker) Model model = Model(); int count = 0; @override Widget build(BuildContext context) { return Actions( actions: >{ ModifyIntent: ModifyAction(model), SaveIntent: SaveAction(model), }, child: Builder( builder: (BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ const Spacer(), Column( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: const Icon(Icons.exposure_plus_1), onPressed: () { Actions.invoke(context, ModifyIntent(++count)); }, ), AnimatedBuilder( animation: model.data, builder: (BuildContext context, Widget? child) { return Padding( padding: const EdgeInsets.all(8.0), child: Text('${model.data.value}', style: Theme.of(context).textTheme.headline4), ); }), IconButton( icon: const Icon(Icons.exposure_minus_1), onPressed: () { Actions.invoke(context, ModifyIntent(--count)); }, ), ], ), SaveButton(model.isDirty), const Spacer(), ], ); }, ), ); } //* ▲▲▲▲▲▲▲▲ code ▲▲▲▲▲▲▲▲ (do not modify or remove section marker) //******************************************************************** }