I have the following example class:
import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'package:angular_components/angular_components.dart'
show
SelectionModel,
HasUIDisplayName,
Selectable,
SelectableOption,
StringSelectionOptions,
MaterialDropdownSelectComponent,
MaterialSelectSearchboxComponent,
SelectionChangeRecord,
ItemRenderer,
CachingItemRenderer;
#Component(
selector: 'example-select',
templateUrl: 'example.html',
styleUrls: const [
'example.css'
],
directives: const [
CORE_DIRECTIVES,
formDirectives,
MaterialDropdownSelectComponent,
MaterialSelectSearchboxComponent,
])
class ExampleSelect {
int width = 0;
List<SelectElement> valuesList;
SelectionOptions<SelectElement> _elementListOptions;
StringSelectionOptions<SelectElement> get elementOptions =>
_elementListOptions;
ItemRenderer<SelectElement> get itemRenderer => _itemRenderer;
// Single Selection Model.
final SelectionModel<SelectElement> singleSelectModel =
new SelectionModel.withList(selectedValues: []);
// Label for the button for single selection.
String get singleSelectLabel => singleSelectModel.selectedValues.isNotEmpty
? itemRenderer(singleSelectModel.selectedValues.first)
: 'No Selection';
dynamic get singleSelectedValue => singleSelectModel.selectedValues.isNotEmpty
? singleSelectModel.selectedValues.first.value
: null;
ExampleSelect() {
singleSelectModel.selectionChanges.listen(updateModel);
valuesList = <SelectElement>[
new SelectElement(1, "First"),
new SelectElement(2, "Second"),
new SelectElement(3, 'Third')
];
_elementListOptions = new SelectionOptions<SelectElement>(valuesList);
}
void updateModel(List<SelectionChangeRecord> record) {
print(record);
}
static final ItemRenderer<SelectElement> _itemRenderer =
new CachingItemRenderer<SelectElement>(
(selectElement) => "$selectElement");
}
class SelectElement implements HasUIDisplayName {
final value;
final String label;
const SelectElement(this.value, this.label);
#override
String get uiDisplayName => label;
#override
bool operator ==(Object other) => other is SelectElement && label == label;
#override
int get hashCode => label.hashCode;
#override
String toString() => uiDisplayName;
}
class SelectionOptions<T> extends StringSelectionOptions<T>
implements Selectable {
SelectionOptions(List<T> options)
: super(options, toFilterableString: (T option) => option.toString());
#override
SelectableOption getSelectable(selectElement) =>
selectElement is SelectElement
? SelectableOption.Selectable
: SelectableOption.Disabled;
}
The html is as follows:
<material-dropdown-select
[buttonText]="singleSelectLabel"
[selection]="singleSelectModel"
[options]="elementOptions"
[width]="width"
[itemRenderer]="itemRenderer">
</material-dropdown-select>
<br>
Selected: {{singleSelectLabel}}
Now, if I run this example, and I select the first element, everything works fine, but when I select the "Second" element, the dropdown return to not selected.
This is resumed by the print function that shows the following:
[SelectionChangeRecord{added: [First], removed: []}]
[SelectionChangeRecord{added: [], removed: [First]}]
What I'm doing wrong?
There is an error in your equals method:
#override
bool operator ==(Object other) => other is SelectElement && label == label;
Do you see it??
Should be
other.label == label
I'd probably suggest not overriding the equals method, but that is what is happening here. So when you select/unselect the widget is selecting/unselecting everything because you said they were equal.
Related
So Im creating this Event Tracker app and I have two screens which are the map and the events list.
I am trying to get the place list to be equal to my places in my App state. Bare in mind that placeList is a modifiable list that I need to add places to this list.
However I am getting a "The default value of an optional parameter must be constant" whenever I initialize this.places=PlaceMapState.placeList and I cant change it to a constant since i need it to update my list of places in the PlaceMapState class and I cant remove it from AppState since I am using it in the PlaceList class to get the places as a list.
I also dont want to remove the AppState entirely because it also contains the map.
Please any solution to this???
Here is my class where I use this list:
class AppState {
AppState({
this.places = PlaceMapState.placeList, //// here is the problem!!!!!!!!!!!!!!!!!!!
this.selectedCategory = PlaceCategory.events,
this.viewType = PlaceTrackerViewType.map,
}) : //assert(places != null),
assert(selectedCategory != null);
List<Place> places;
PlaceCategory selectedCategory;
PlaceTrackerViewType viewType;
AppState copyWith({
List<Place> places,
PlaceCategory selectedCategory,
PlaceTrackerViewType viewType,
}) {
return AppState(
selectedCategory: selectedCategory ?? this.selectedCategory,
viewType: viewType ?? this.viewType,
);
}
static AppState of(BuildContext context) => AppModel.of<AppState>(context);
static void update(BuildContext context, AppState newState) {
AppModel.update<AppState>(context, newState);
}
static void updateWith(
BuildContext context, {
List<Place> places,
PlaceCategory selectedCategory,
PlaceTrackerViewType viewType,
}) {
update(
context,
AppState.of(context).copyWith(
places: places,
selectedCategory: selectedCategory,
viewType: viewType,
),
);
}
#override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
return other is AppState &&
other.places == places &&
other.selectedCategory == selectedCategory &&
other.viewType == viewType;
}
#override
int get hashCode => hashValues(places, selectedCategory, viewType);
}
Here is the class of placeList where I use places to get a list:
class PlaceList extends StatefulWidget {
const PlaceList({Key key}) : super(key: key);
#override
PlaceListState createState() => PlaceListState();
}
class PlaceListState extends State<PlaceList> {
ScrollController _scrollController = ScrollController();
void _onCategoryChanged(PlaceCategory newCategory) {
_scrollController.jumpTo(0.0);
AppState.updateWith(context, selectedCategory: newCategory);
}
void _onPlaceChanged(Place value) {
// Replace the place with the modified version.
final newPlaces = List<Place>.from(AppState.of(context).places);
final index = newPlaces.indexWhere((place) => place.id == value.id);
newPlaces[index] = value;
AppState.updateWith(context, places: newPlaces);
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
_ListCategoryButtonBar(
selectedCategory: AppState.of(context).selectedCategory,
onCategoryChanged: (value) => _onCategoryChanged(value),
),
Expanded(
child: ListView(
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 8.0),
controller: _scrollController,
shrinkWrap: true,
children: AppState.of(context)
.places //this the places im talking about!!!!!!!!!!!!!!!!!
.where((place) =>
place.category == AppState.of(context).selectedCategory)
.map((place) => _PlaceListTile(
place: place,
onPlaceChanged: (value) => _onPlaceChanged(value),
))
.toList(),
),
),
],
);
}
}
A common workaround to requiring constant values for default arguments is to instead use a sentinel argument as the default that can be const. Typically that sentinel argument can be null:
class AppState {
AppState({
List<Place>? places,
...
}) : places = places ?? PlaceMapState.placeList;
List<Place> places;
...
}
Here is how the main TodoApp widget looks like:
class TodoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(title: 'Todo List', home: new TodoList());
}
}
I have created a class for a list item which is called Task:
class Task {
String name;
int number;
Task(this.name, this.number);
}
The list widget looks like this:
class TodoList extends StatefulWidget {
#override
createState() => new TodoListState();
}
The TodoListState class has a method which adds new items to the list:
class TodoListState extends State<TodoList> {
List<Task> _todoItems = [];
List<bool> checkedItems = []; // this was created for testing purposes
void _addTodoItem(Task task) {
if (task.name.length > 0) {
setState(() => checkedItems.add(false)); // works fine
setState(() => _todoItems.add(new Task('abc', 12))); // does not work
}
}
...
}
Why when _addTodoItem method is called, one item is added to the checkedItems list but _todoItems list is left unchanged? What am I missing?
It's working as it should, setState is only called once, hence the first method is executed. You should add both methods in the setState method, since it's called once per run or state, you can write it like this:
....
setState(() => {
checkedItems.add(false);
_todoItems.add(new Task('abc', 12);
});
....
I have a longer input form where the user can add a number of identical subforms.
For e.g. think about a form where parents can enter their data (parentform) and afterwards add their childs. Since i don't know how many childs a user has they can dynamically add more child forms using a button.
For each child the requested data is identical so e.g. name, birthday, gender, in reality its about 10 fields. So i created a separate widget (childform).
If the user now saves the outer form i need to gather all information from the childforms. I now i can do this by creating all TextControllers inside the parentform, save them in a list and insert them when creating the childforms, like so:
void addTextField() {
int index = _childNameControllers.length;
_childNameControllers.add( TextEditingController());
_childforms.add(
RemovableEntityField(
nameController: _childNameControllers[index]
)
);
}
And then on save something like
void onSave() {
List<Childs> _childs = [];
_childNameControllers.forEach((controller) {
if (controller.text.trim().isNotEmpty) {
_childs.add(Child(name: name));
}
});
// do something with the data
}
But as i said i have about 10 Fields per childform and i would have to create 10 controllers for each form and would need 10 parameters in the childform just to read this information.
Is there an easier way to do this ?
PS: i know i could make the child state public but i don't really want to do this either
I believe you are supposed to add variable updater in the child and push the value up to the parent.
https://github.com/flutter/flutter/tree/4b4cff94bf6631bf6326c9239c10b286b4fdb08c/dev/benchmarks/test_apps/stocks
The flutter stock application has an example.
In the child you need this.
class StockSettings extends StatefulWidget {
const StockSettings(this.configuration, this.updater);
final StockConfiguration configuration;
final ValueChanged<StockConfiguration> updater;
#override
StockSettingsState createState() => StockSettingsState();
}
class StockSettingsState extends State<StockSettings> {
void _handleBackupChanged(bool value) {
sendUpdates(widget.configuration.copyWith(backupMode: value ? BackupMode.enabled : BackupMode.disabled));
}
void sendUpdates(StockConfiguration value) {
if (widget.updater != null)
widget.updater(value);
}
In the parent, you pass down configuartion updator which is just a wrapper around set state
class StocksAppState extends State<StocksApp> {
StockData stocks;
StockConfiguration _configuration = StockConfiguration(
stockMode: StockMode.optimistic,
backupMode: BackupMode.enabled,
debugShowGrid: false,
debugShowSizes: false,
debugShowBaselines: false,
debugShowLayers: false,
debugShowPointers: false,
debugShowRainbow: false,
showPerformanceOverlay: false,
showSemanticsDebugger: false
);
#override
void initState() {
super.initState();
stocks = StockData();
}
void configurationUpdater(StockConfiguration value) {
setState(() {
_configuration = value;
});
}
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => StockHome(stocks, _configuration, configurationUpdater),
'/settings': (BuildContext context) => StockSettings(_configuration, configurationUpdater)
},
In google IO 18, the Flutter presenters have showed a feature but have not showed how to implement this.
The video (at exact time) is: https://youtu.be/RS36gBEp8OI?t=1776
How to implement such thing? How can I properly make the Stream to be correctly formatted based on a Sink?
(sorry but I am not too familiar with Rx)
Use the combineLatest function from the rxdart package. It takes the latest values of input streams, so any time either the locale or cart items change it will calculate and format the total cost.
import 'dart:async'; // Sink, Stream
import 'dart:ui'; // Locale
import 'package:rxdart/rxdart.dart'; // Observable, *Subject
class Bloc {
var _locale = BehaviorSubject<Locale>(seedValue: Locale('en', 'US'));
var _items = BehaviorSubject<List<CartItem>>(seedValue: []);
Stream<String> _totalCost;
Sink<Locale> get locale => _locale.sink;
Stream<List<CartItem>> get items => _items.stream;
Stream<String> get totalCost => _totalCost;
Bloc() {
_totalCost = Observable.combineLatest2<Locale, List<CartItem>, String>(
_locale, _items, (locale, items) {
// TODO calculate total price of items and format based on locale
return 'USD 10.00';
}).asBroadcastStream();
}
void dispose() {
_locale.close();
_items.close();
}
}
Disclaimer: I didn't try to run this code so there might be errors but the basic idea should be solid.
The best candidate for doing this cross-platform is NumberFormat from the intl package. However you still have to pass it a locale string ("en_US") and ISO 4217 currency code ("USD").
After a little digging I couldn't find this information in any Dart package. The NumberFormat class has a private map for looking up a currency symbol ("$") from a currency code, but keys of the map, the currency codes, are inaccessible. So I decided to make a package that makes locale strings and currency codes available.
currency_bloc.dart
import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'package:intl/intl.dart';
import 'package:locales/locales.dart';
import 'package:locales/currency_codes.dart';
class LocalCurrency {
const LocalCurrency(this.locale, this.code);
final Locale locale;
final CurrencyCode code;
#override toString() => '$code ($locale)';
#override operator==(o) => o is LocalCurrency && o.locale == locale && o.code == code;
#override hashCode => toString().hashCode;
}
/// Emits currency strings according to a locale.
class CurrencyBloc {
// Inputs.
final _valueController = StreamController<double>();
final _currencyController = StreamController<LocalCurrency>();
// Outputs.
final _currency = BehaviorSubject<String>();
/// The last formatted currency value emitted from the output stream.
String lastCurrency;
// For synchronously receiving the latest inputs.
double _value;
NumberFormat _formatter;
CurrencyBloc({LocalCurrency initialCurrency, double initialValue}) {
_valueController.stream
.distinct()
.listen((value) => _updateCurrency(value: value));
_currencyController.stream
.distinct()
.listen((currency) => _updateCurrency(currency: currency));
// Initialize inputs.
locale.add(initialCurrency ??
LocalCurrency(Locale.en_US, CurrencyCode.usd));
value.add(initialValue ?? 0.0);
}
void dispose() {
_valueController.close();
_currencyController.close();
_currency.close();
}
_updateCurrency({double value, LocalCurrency currency}) {
if (currency != null) {
_formatter = NumberFormat.simpleCurrency(
locale: '${currency.locale}',
name: '${currency.code}',
decimalDigits: 2);
}
if (value != null) {
_value = value;
}
if (_value != null && _formatter != null) {
lastCurrency = _formatter.format(_value);
_currency.add(lastCurrency);
}
}
/// Change the current [Locale] and/or [CurrencyCode].
Sink<LocalCurrency> get locale => _currencyController.sink;
/// Change the the value to be formatted.
Sink<double> get value => _valueController.sink;
/// Formatted currency.
Stream<String> get currency => _currency.stream;
}
currency_provider.dart (conventional)
class CurrencyProvider extends InheritedWidget {
CurrencyProvider({Key key, #required this.bloc, #required Widget child})
: super(key: key, child: child);
final CurrencyBloc bloc;
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static CurrencyBloc of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(CurrencyProvider) as CurrencyProvider)
.bloc;
}
Example usage
...
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CurrencyBloc bloc;
#override
Widget build(BuildContext context) =>
CurrencyProvider(bloc: bloc, child: CurrencyExample());
#override
void initState() {
super.initState();
bloc = CurrencyBloc();
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
void didUpdateWidget(StatefulWidget oldWidget) {
super.didUpdateWidget(oldWidget);
bloc.dispose();
bloc = CurrencyBloc();
}
}
class CurrencyExample extends StatelessWidget {
final controller = TextEditingController();
#override
Widget build(BuildContext context) {
final bloc = CurrencyProvider.of(context);
return ListView(
children: <Widget>[
TextField(controller: controller),
StreamBuilder(
stream: bloc.currency,
initialData: bloc.lastCurrency,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else if (snapshot.hasError) {
return new Text('${snapshot.error}');
}
return Center(child: CircularProgressIndicator());
}),
FlatButton(
child: Text('Format Currency'),
onPressed: () => bloc.value.add(double.tryParse(controller.text)),
)
],
);
}
}
I would like to detect a property change from one object and then forward that value (or recompute the value and pass the result) to another object's property. I saw the example from the documentation which demonstrates value forwarding:
class MyModel extends Observable {
StreamSubscription _sub;
MyOtherModel _otherModel;
MyModel() {
...
_sub = onPropertyChange(_otherModel, #value,
() => notifyPropertyChange(#prop, oldValue, newValue);
}
String get prop => _otherModel.value;
set prop(String value) { _otherModel.value = value; }
}
But I don't know where to get the oldValue and newValue from.
I suppose those should be passed as parameters to the callback of onPropertyChange (the third parameter), but that is not the case. The callback provides no parameters. Is this an oversight or am I missing something ?
import 'dart:async';
import 'package:observe/observe.dart';
class MyOtherModel extends Object with Observable {
#observable
String value;
}
class MyModel extends Object with Observable {
StreamSubscription _sub;
MyOtherModel _otherModel = new MyOtherModel();
MyModel() {
///...
_otherModel.changes.listen((crs) {
crs.forEach((PropertyChangeRecord cr) =>
notifyPropertyChange(#prop, cr.oldValue, cr.newValue));
});
}
String get prop => _otherModel.value;
set prop(String value) => _otherModel.value = value;
}
void main() {
MyModel m = new MyModel();
m.prop = 'bla';
m.changes.listen(print);
// initiate change notification
Observable.dirtyCheck();
}
output
[#<PropertyChangeRecord Symbol("value") from: null to: bla>]