The package contains the following decorating classes: Property<T>
, Command
, ParameterizedCommand<T>
, AsyncCommand
, ParameterizedAsyncCommand<T>
. These classes can be useful when creating viewmodels based on classes extending ChangeNotifier.
Properties and commands are defined in the viewmodel class class FirstPageNotifier extends ChangeNotifier
.
To store the counter value
late final outputProperty = Property<int>(
initialValue: 0,
notifyListeners: notifyListeners,
);
The initial value of the property is set and the method notifyListeners()
is called if
the value of this property changes.
In the build(BuildContext context)
method of the FirstPage extends StatelessWidget
class
we get a tracked reference to the property value
final output =
FirstPageInheritedNotifier.watchNotifier(context).outputProperty.value;
the output of the value is as follows
Text(
output.toString(),
style: Theme.of(context).textTheme.headlineLarge,
),
Two commands have been created for increment and decrement of the counter
late final incrementCommand = Command(
action: () => outputProperty.value += 1,
canAction: () => outputProperty.value < 3,
);
late final decrementCommand = Command(
action: () => outputProperty.value -= 1,
canAction: () => outputProperty.value > 0,
);
Methods that change the value of the counter property are passed as arguments for the action
parameter.
Methods that limit the range of counter values are passed as arguments for the canAction
parameter, which restricts the availability of the command to execute.
In the FirstPage
class in the build(BuildContext context)
method getting links to commands
final incrementCommand =
FirstPageInheritedNotifier.readNotifier(context).incrementCommand;
final decrementCommand =
FirstPageInheritedNotifier.readNotifier(context).decrementCommand;
Using commands to operate buttons
ElevatedButton(
onPressed: decrementCommand.canExecute()
? decrementCommand.execute
: null,
child: const Icon(Icons.exposure_minus_1),
),
and
ElevatedButton(
onPressed: incrementCommand.canExecute()
? incrementCommand.execute
: null,
child: const Icon(Icons.plus_one),
),
You can see that in the method of the onPressed
parameter the possibility of executing the command is checked which affects the availability of the button for clicking.
The properties and command are defined in the viewmodel class SecondPageNotifier extends ChangeNotifier
.
To display the counter value, one property is defined, which is set only by its initial value.
final outputProperty = Property<int>(initialValue: 0);
In the SecondPage
class in the build(BuildContext context) method
getting a tracked reference to the property value
final output =
SecondPageInheritedNotifier.watchNotifier(context).outputProperty.value;
for displaying the property value
Text(
output.toString(),
style: Theme.of(context).textTheme.headlineLarge,
),
One common parameterized command is defined for the buttons,
which, when called for execution, takes an int
value as an argument.
late final changeCommand = ParameterizedCommand<int>(
action: (value) => outputProperty.value += value,
notifyListeners: notifyListeners,
);
When executing the command method, notifyListeners()
is called.
In the SecondPage
class in the build(BuildContext context)
method we get a link to the command
final command =
SecondPageInheritedNotifier.readNotifier(context).changeCommand;
Using the command for buttons looks like this
ElevatedButton(
onPressed: () => command(-1),
child: Text(
'-1',
style: Theme.of(context).textTheme.titleLarge,
),
),
...
ElevatedButton(
onPressed: () => command(3),
child: Text(
'3',
style: Theme.of(context).textTheme.titleLarge,
),
),
In the viewmodel class ThirdPageNotifier extends ChangeNotifier
, three properties and a command are defined.
The CheckboxListTile
property is set with the notifyListeners()
call.
late final isEnabledProperty = Property<bool>(
initialValue: false,
notifyListeners: notifyListeners,
);
Next, in _Third Page State
we get a tracked reference to this property
final isEnabledProperty =
ThirdPageInheritedNotifier.watchNotifier(context).isEnabledProperty;
and we use it this way
CheckboxListTile(
title: const Text('enable the input line'),
controlAffinity: ListTileControlAffinity.platform,
contentPadding: const EdgeInsets.all(50),
value: isEnabledProperty.value,
onChanged: (value) {
isEnabledProperty.value = value!;
},
),
A property is set for TextField
with a call to notifyListeners()
and verification rules.
late final inputProperty = Property<String>(
initialValue: '',
notifyListeners: notifyListeners,
verificationRules: <String, bool Function(String)>{
'The value cannot be an empty string': (value) => value.isEmpty,
'The length of the string cannot be less than 3 characters': (value) =>
value.length < 3,
},
);
For the verification rule, a text message for the user is specified as the key,
and the method returning true
is specified as the value.
In the InputTextWidget
we get references to the properties
final isEnabledProperty =
ThirdPageInheritedNotifier.watchNotifier(context).isEnabledProperty;
final inputProperty =
ThirdPageInheritedNotifier.readNotifier(context).inputProperty;
and we use it this way
TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: 'Enter the Value',
errorText: inputProperty.hasErrors ? inputProperty.errors[0] : null,
),
enabled: isEnabledProperty.value,
controller: controller,
onChanged: (text) {
inputProperty.value = text;
},
),
In this line errorText: inputProperty.hasErrors ? inputProperty.errors[0] : null,
the
logic of displaying error messages to the user when entering data is defined.
The accessibility of the TextField
for input is tied to the value of the CheckBoxListTile
via
the isEnabledProperty
property in this line enabled: isEnabledProperty.value,
.
The inputProperty
property updates its value in the method defined for onChanged
.
A simple property is used to output the result
final outputProperty = Property<String>(initialValue: '');
.
The example uses one asynchronous command for the button
late final submitCommand = AsyncCommand(
action: () async {
await Future.delayed(const Duration(milliseconds: 100));
outputProperty.value = inputProperty.value;
inputProperty.value = '';
isEnabledProperty.value = false;
},
canAction: () => inputProperty.hasErrors == false,
notifyListeners: notifyListeners,
);
The availability of the button depends on the presence of errors when entering using
the parameter canAction: () => inputProperty.hasErrors == false,
.
When executing the command method, notifyListeners()
is called.
In _ThirdPageState
we get a link to the command
final submitCommand =
ThirdPageInheritedNotifier.readNotifier(context).submitCommand;
and we use the command for the button in this way.
ElevatedButton(
onPressed: submitCommand.canExecute()
? (() async {
await submitCommand.execute();
controller.clear();
})
: null,
child: const Icon(Icons.done),
),
One property and three commands are defined in the FourthPageNotifier extends ChangeNotifier
viewmodel class.
A property has been created to display the list.
final peopleProperty = Property<List<Person>>(
initialValue: <Person>[],
);
In the PeopleListViewWidget
we get a tracked reference to the property.
final people =
FourthPageInheritedNotifier.watchNotifier(context).peopleProperty.value;
Displaying a collection of people using ListView.builder()
and ListTile
.
ListView.builder(
itemCount: people.length,
itemBuilder: (context, index) {
final person = people[index];
return ListTile(
onTap: () async {...},
title: Text(person.fullName),
subtitle: Text('ID: ${person.id}'),
trailing: TextButton(...),
);
},
),
To implement CRUD operations on a collection of people , three parameterized asynchronous commands have been created: addCommand
, removeCommand
, updateCommand
.
Example on addCommand
.
late final addCommand = ParameterizedAsyncCommand<List<String>>(
action: (value) async {
await _db.create(names: value);
peopleProperty.value = await _db.getPeople();
},
notifyListeners: notifyListeners,
);
In Composerwidget
in the Widget build(BuildContext context) method
we get a link
to the command.
final command =
FourthPageInheritedNotifier.readNotifier(context).addCommand;
We use the command in this way
TextButton(
onPressed: () async {
final names = <String>[
firstNameController.text,
lastNameController.text,
];
await command(names);
firstNameController.clear();
lastNameController.clear();
},
child: Text(
'Add to list',
style: Theme.of(context).textTheme.titleMedium,
))