I am developing an application but I noticed that each class has a lot of similar code to the one below addRace method in the class below:
#CustomTag( 'race-view' )
class RaceViewForm extends PolymerElement
{
RaceViewForm.created() : super.created();
void addRace( Event e, var detail, Element target )
{
var raceElem = $['race'];
if( raceElem.children.length < 1 )
{
raceElem.children.add( new Element.tag( 'race-form' ) );
}
raceElem.on[ DELETE_BUTTON_FORM_EVENT ]
.listen( (Event e)
{
(e.target as Element).remove();
dispatchEvent( new CustomEvent( RACE_VIEW_EVENT, detail:new Race() ));
});
}
}
I have attempted to move the repeating code into a library called shared.dart with the following refactoring:
import 'dart:html';
import 'package:observe/observe.dart';
...
void addForm( Element target, String eventName, String dispatchEventName, dynamic instance )
{
var element = target;
if( element.children.length < 1 )
{
element.children.add( new Element.tag( 'race-form' ) );
}
element.on[ eventName ]
.listen( (Event e)
{
(e.target as Element).remove();
dispatchEvent( new CustomEvent( dispatchEventName, detail:instance ));
});
}
However, the dart IDE flags the dispatchEvent method in the addForm method as being not defined with parameter onData (dynamic) -> void. Given that dispatchEvent is in the 'dart:html' package, I am uncertain as to what to do next.
I didn't find a dispatchEvent() in dart:html (top level) only as method of the Node class. You need to pass your Polymer element instance to the library method.
You could implement it as a mixin to make it easier to reuse.
See https://github.com/donny-dont/Pixelate/blob/master/lib/components/expander/expander.dart#L41 (Expandable, Customizeable) for an example.
Related
I'm writing a code generator for Dart using the build_runner, but my builder is not being called for annotations at fields, although it does work for annotations at classes.
Is it possible to also call the generator for annotations at fields (or at any place for that matter)?
For example, the builder is called for the following file:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
int number;
}
But not for this one:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
class Fruit {
#MyAnnotation()
int number;
}
Here's the definition of the annotation:
class MyAnnotation {
const MyAnnotation();
}
And this is how the generator is defined. For now, it just aborts whenever it's called, causing an error message to be printed.
library my_annotation_generator;
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:my_annotation/my_annotation.dart';
import 'package:source_gen/source_gen.dart';
Builder generateAnnotation(BuilderOptions options) =>
SharedPartBuilder([MyAnnotationGenerator()], 'my_annotation');
class MyAnnotationGenerator extends GeneratorForAnnotation<MyAnnotation> {
#override
generateForAnnotatedElement(Element element, ConstantReader annotation, _) {
throw CodeGenError('Generating code for annotation is not implemented yet.');
}
Here's the build.yaml configuration:
targets:
$default:
builders:
my_annotation_generator|my_annotation:
enabled: true
builders:
my_annotation:
target: ":my_annotation_generator"
import: "package:my_annotation/my_annotation.dart"
builder_factories: ["generateAnnotation"]
build_extensions: { ".dart": [".my_annotation.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
At least from my experience, your file 'example.dart' would need at least one annotation above the class definition to be parsed by GeneratorForAnnotation.
example.dart:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
#MyFieldAnnotation()
int number;
}
To access annotations above class fields or class methods you could use a visitor to "visit" each child element and extract the source code information.
For example, to get information about the class fields you could override the method visitFieldElement and then access any annotations using the getter: element.metadata.
builder.dart:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';
import 'package:my_annotation/my_annotation.dart';
class MyAnnotationGenerator extends
GeneratorForAnnotation<MyAnnotation> {
#override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,){
return _generateSource(element);
}
String _generateSource(Element element) {
var visitor = ModelVisitor();
element.visitChildren(visitor);
return '''
// ${visitor.className}
// ${visitor.fields}
// ${visitor.metaData}
''';
}
}
class ModelVisitor extends SimpleElementVisitor {
DartType className;
Map<String, DartType> fields = {};
Map<String, dynamic> metaData = {};
#override
visitConstructorElement(ConstructorElement element) {
className = element.type.returnType;
}
#override
visitFieldElement(FieldElement element) {
fields[element.name] = element.type;
metaData[element.name] = element.metadata;
}
}
Note: In this example, _generateSource returns a commented statement. Without comments you would need to return well-formed dart source code, otherwise, the builder will terminate with an error.
For more information see:
Source Generation and Writing Your Own Package (The Boring Flutter Development Show, Ep. 22) https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s
The built-in GeneratorForAnnotation uses the LibraryElement's annotatedWith(...) method, which only checks for top-level annotations.
To also detect annotations on fields, you'll need to write something custom.
Here's the Generator I wrote for my project:
abstract class GeneratorForAnnotatedField<AnnotationType> extends Generator {
/// Returns the annotation of type [AnnotationType] of the given [element],
/// or [null] if it doesn't have any.
DartObject getAnnotation(Element element) {
final annotations =
TypeChecker.fromRuntime(AnnotationType).annotationsOf(element);
if (annotations.isEmpty) {
return null;
}
if (annotations.length > 1) {
throw Exception(
"You tried to add multiple #$AnnotationType() annotations to the "
"same element (${element.name}), but that's not possible.");
}
return annotations.single;
}
#override
String generate(LibraryReader library, BuildStep buildStep) {
final values = <String>{};
for (final element in library.allElements) {
if (element is ClassElement && !element.isEnum) {
for (final field in element.fields) {
final annotation = getAnnotation(field);
if (annotation != null) {
values.add(generateForAnnotatedField(
field,
ConstantReader(annotation),
));
}
}
}
}
return values.join('\n\n');
}
String generateForAnnotatedField(
FieldElement field, ConstantReader annotation);
}
I had a very similar issue trying to target specific methods within my annotated classes. Inspired by your answers I slightly modified the class annotation model_visitor to check the method annotation before selecting elements.
class ClassAnnotationModelVisitor extends SimpleElementVisitor<dynamic> {
String className;
Map<String, String> methods = <String, String>{};
Map<String, String> parameters = <String, String>{};
#override
dynamic visitConstructorElement(ConstructorElement element) {
final elementReturnType = element.type.returnType.toString();
className = elementReturnType.replaceFirst('*', '');
}
#override
dynamic visitMethodElement(MethodElement element) {
if (methodHasAnnotation(MethodAnnotation, element)) {
final functionReturnType = element.type.returnType.toString();
methods[element.name] = functionReturnType.replaceFirst('*', '');
parameters[element.name] = element.parameters.map((e) => e.name).join(' ,');
}
}
bool methodHasAnnotation(Type annotationType, MethodElement element) {
final annotations = TypeChecker.fromRuntime(annotationType).annotationsOf(element);
return !annotations.isEmpty;
}
}
Then, I can use the basic GeneratorForAnnotation class and generate for class and methodsArray.
I´m quite new to dart and wondering what this "wrapped" function exactly does?
It´s called like a normal function with "connectUnits(userRepo)":
void Function(
Store<AppState> store,
dynamic action,
NextDispatcher next,
) connectUnits(
UnitsRepository unitsRepository,
) {
return (store, action, next) {
unitsRepository.units().listen((units) {
store.dispatch(LoadUnitsAction(units));
next(action);
});
};
}
Thanks & best,
Michael
Functions are first class citizens in Dart. Your example defines a function named connectUnits that returns a function with a signature void Function(Store<AppState> store, dynamic action, NextDispatcher next).
To better understand, your code is the same as:
// define a kind of function
typedef MyFunction = void Function(Store<AppState> store, dynamic action, NextDispatcher next);
MyFunction connectUnits(UnitsRepository unitsRepository) {
return (store, action, next) {
unitsRepository.units().listen((units) {
store.dispatch(LoadUnitsAction(units));
next(action);
});
};
}
I have a problem/question regarding the bloc plattern with flutter.
Currently, i am starting my app like this
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return BlocProvider(
bloc: MyBloc(),
child: MaterialApp(
title: "MyApp",
home: MyHomePage(),
routes: {
'/homePage': (context) => MyHomePage(),
'/otherPage': (context) => OtherPage(),
'/otherPage2': (context) => OtherPage2(),
...
},
));
So that i can retrieve/access myBloc like
myBloc = BlocProvider.of(context) as MyBloc;
and the data represented by the state like
BlocBuilder<MyBlocEvent, MyObject>(
bloc: myBloc,
builder: (BuildContext context, MyObject myObject) {
....
var t = myObject.data;
....
myBloc.onFirstEvent();
...
};
wherever i need it.
MyBloc is implemented like this:
abstract clas MyBlocEvent {}
class FirstEvent extends MyBlocEvent {}
class SecondEvent extends MyBlocEvent {}
class MyBloc extends Bloc<MyBlocEvent , MyObject>
void onFirstEvent()
{
dispatch(FirstEvent());
}
void onSecondEvent()
{
dispatch(SecondEvent());
}
#override
Stream<MyObject> mapEventToState( MyObject state, MyBlocEvent event) async* {
if (event is FirstEvent) {
state.data = "test1";
}
else if (event is SecondEvent) {
state.otherData = 5;
}
yield state;
}
The problem i now have, is that as soon as i change on of the state values and call
Navigator.pop(context)
to go back in the current stack, i can't change anything is the state anymore because the underlying stream seems to be closed. It fails with the message:
Another exception was thrown: Bad state: Cannot add new events after calling close"
Now this only happens after i call pop. If i only push new screens i can happily change the state data without any problems.
Am i doing something wrong regarding the Navigation here or is there something else i didn't catch regarding flutter or the bloc pattern itself?
Bad state: Cannot add new events after calling close
This error means that you are calling add on a StreamController after having called close:
var controller = StreamController<int>();
controller.close();
controller.add(42); // Bad state: Cannot add new events after calling close
It is likely related to you calling close inside the dispose method the "wrong" widget.
A good rule of thumb is to never dispose/close an object outside of the widget that created it. This ensure that you cannot use an object already disposed of.
Hope this helps in your debugging.
The navigation of the app depends on your widget designs.
I use stateless widgets and render the view using bloc's data.
Whenever i navigate to another page, i would pop the current widget and navigate to the next widget.
The next stateless widget declare the bloc,
then in your subsequent stateless widgets should contain calls like MyBloc.dispatch(event(param1: value1, param2: value2));
In MyBloc, you need to set the factory of your state that contains final values;
#override
Stream<MyObject> mapEventToState( MyObject state, MyBlocEvent event) async* {
if (event is FirstEvent) {
// set it in the state, so this code is omitted
// state.data = "test1";
// add this
yield state.sampleState([], "test1");
}
else if (event is SecondEvent) {
// state.otherData = 5;
yield state.sampleState([], 5);
} else {
yield state.sampleState([], null);
}
The MyObjectState needs to be setup like this,
class MyObjectState {
final List<Bar> bars;
final String Foo;
const MyObjectState(
{this.bars,
this.foo,
});
factory MyObjectState.sampleState(List<Bar> barList, String value1) {
return MyObjectState(bars: barList, foo: message);
}
}
So that the stateless widget can use the bloc like this
MyBloc.currentState.sampleState.foo
You can try run Felix Angelov's flutter project.
Login Flow Example
I have some code:
// main.dart:
void main{
initPolymer();
var view = new ChatAppConsumer();
}
//chat_app.dart
#CustomTag('chat-app')
class ChatApp extends PolymerElement{
ChatApp.created():super.created();
}
class ChatAppConsumer{
final ChatApp view = new Element.tag('chat-app');
}
as far as I can tell I have all my files properly referenced and Im calling initPolymer(); before I attempt to create my custom tag, but I get the type error that the HtmlElement returned by new Element.tag('chat-app'); is not of typeChatApp` but I use this exact same pattern in another package I have and it works perfectly there. Anyone come across something like this before?
initPolymer is not enough, you should pass a closure to initPolymer.run(() => ...) which executes your Polymer related code.
See how to implement a main function in polymer apps for more details
= Polymer 0.16.0
// main.dart:
void main{
initPolymer().then((zone) => zone.run(() {
var view = new ChatAppConsumer();
}));
}
< Polymer 0.16.0
// main.dart:
void main{
initPolymer().run(() {
var view = new ChatAppConsumer();
});
}
Hello: I need pass a function (event handler) as parameter to other function site in other class:
classB
{
ClassB(){}
/***/
load (void onData(Event e))
{
ImageElement ie=new ImageElement();
ie.onLoad.listen( onData );
ie.src='hello.png';
}
/***/
}
classA
{
List<ClassB> lcb=new List();
ClassA(){}
/****/
void handler(Event e) {
/****/
}
myFunction() {
/***/
for (var i=0; i < lcb.length; i++)
{
***
lcb[i].load( handler );
***
}
/***/
}
}
It seems right in principle, but does not work. The function that is passed is never executed.
Anyone have any idea what is the correct way?
Looks good and should work especially after your recent update with listen.
Why do you expect the ImageElement to fire. You have to assign a src and attach it to the DOM to make it load and fire onload.