f2prateek / dart Goto Github PK
View Code? Open in Web Editor NEWExtras binding and intent builders for Android apps.
License: Apache License 2.0
Extras binding and intent builders for Android apps.
License: Apache License 2.0
Suppose we have an @Optional @InjectExtra boolean foo
. Can we use foo = false/true
to initialize a default value in case the field is not found ?
This is missing in the documentation.
To be fixed before RC 2 :
class A extends Activity {
@InjectExtra String s;
}
class B extends A {
@InjectExtra String t;
}
Henson's generated DSL should include the inherited s
field;
Note : if B doesn't define any @InjectExtra annotated field, the DSL is not even generated for it...
@f2prateek do you already have a mechanism to collect inherited fields in a given class ?
so that even if a class doesn't contain @InjectExtra
annotated fields, Henson can still allow navigation to it (@Henson
would be added at the class level).
Otherwise those activities would have to be started the standard way which make code less homogenous, and may be more error-prone when an @InjectExtra
annotated field is added.
I'm using Dart in my BaseFragment
implementation, but some of my fragments use arguments without injections. Doing so, I'm getting an UnableToInjectException
because there's no annotations defined...
The correct behavior should be to silently return.
InjectExtra should have CLASS Retention, not runtime.
It's the way one would test an app using DART 2 and the mockable jars unit testing system from Google, add an example.
It would remove the need to use Robolectric and speed up tests.
I can't find out how to use henson, the readme is quite shallow, i got a activity annotated with Henson annotation, but the generated navigator don't have any extras options, and if i use InjectExtra on any field of the activity i got the following exception:
Error:(30, 8) error: @henson class MainActivty must not contain any @InjectExtra annotation
How can i correctly use henson with extras?
For instance if one declares @InjectExtra("a.b"), the intent builder generates code :
public class LocalizedMobileAppService$$IntentBuilder {
private Intent intent;
private Bundler bundler = Bundler.create();
public LocalizedMobileAppService$$IntentBuilder(Context context) {
intent = new Intent(context, LocalizedMobileAppService.class);
}
//a.b is not valid Java identifier
public LocalizedMobileAppService$$IntentBuilder.AllSet LocationInfo(LocationInfo a.b) {
bundler.put("a.b", a.b);
return new LocalizedMobileAppService$$IntentBuilder.AllSet();
}
public class AllSet {
public Intent build() {
intent.putExtras(bundler.get());
return intent;
}
}
}
And this code doesn't compile.
There're big chances that ButterKnife is used along Dart, and I spent 45 minutes to debug that "Optional" thing. I was using the Butterknife's annotation on my injected fields...
Because the name collides, I had to prefix those annotations with the package name, and my fields really looks ugly now.
After merging : https://github.com/f2prateek/dart/pull/76/files
@johncarl81 @iainconnor
we see a problem : devs need Parceler to serialize something like an ArrayList. It would even try to use Parceler if it is not available, making the intentfactory and extrainjector fail.
Good catch @dlemures !
So, we would like to make things a bit more subtile :
@Macarse @johncarl81 @f2prateek, please check :
The problem is the following. Let's say we have
class A {
@InjectExtra String s;
}
@Henson class B {
}
It would be possible, only in the case of a subclass, to use the DSL of class A
to build an intent for class B
. Some conditions apply, they are discussed later on.
It would allow to do something like (pseudo code):
if (condition) {
gotoA();
} else {
gotoB();
}
For now, the only way to perform such constructs is to build fully independent intents to go to both A
and B
.
The proposed enhanced DSL syntax would be
Henson.with(context)
.gotoA()
.extendTo(B.class)
.build();
The generated code would then build a normal intent to goto A
but would ultimately invoke setClass(context, B.class)
on the intent being built when the verb extendTo
is invoked.
It is important to emphasize that such use case can actually follow a normal good-practice of OO inheritance. However, in order to promote good practices and avoid intent mess, we should not allow to extend the DSL to classes that don't have an inheritance link. That would be a very bad design to share same intent keys for unrelated classes and the would destroy constraint enforcement checking philosophy of Henson.
B
extends A
.B
contains no non-optional @InjectExtra
annotated fields (as this would imply an intent to A
would only build an incomplete intent to B). Optional fields are allowed though. This can also be checked at compile-time.Nevertheless, the call to extendTo
will be executed at runtime only. We won't be able to perform the checks before runtime and it will require reflection (which breaks the Dart philosophy).
--> See proposal enhancement for compile-time check.
We could slightly adapt the former proposal to perform a compile-time constraint verification :
Henson.with(context)
.gotoA()
.extendToB()
.build();
In that case, the generated code would allow to switch to B if and only if all conditions described above have been met. Otherwise the DSL extension will not be generated.
It is probably possible to return the internal state B.AllSet
after extendToB()
. There are maybe some additional constraints but it looks feasible. In that case, after the call to extendToB()
it would even be possible use the B$$IntentBuilder
DSL to set optional extras of B
:
Henson.with(context)
.gotoA()
.extendToB()
.optionalFooInB("foo");
.build();
In that case the initial condition would be solved by the very elegant :
A.AllSet state = Henson.with(context)
.gotoA();
if (condition) {
state.extendToB()
.optionalFooInB("foo");
.build()
}
This proposal conflicts with #54.
Or maybe the best would be to allow abstract classes to generate a DSL if and only if they are extended (which would change the final state of the DSL state machine). Indeed an abstract class DSL would not provide the build method, only subclasses would, in case we can implement the enhanced proposal.
Dart 2.0 RC 3
Henson is gonna be generated only in case all works, otherwise you got hundreds of errors
Maybe we could enhance the code generator, to be robust to one activity's builder to fail to be generated in that case Henson would still get generated with only the missing methods for a single failed class that would make it easier to fix.
Thx to Danny Preussler (@dpreussler) for this !
The support-annotations library ships with @nullable and there's a whole bunch in other annotation libraries. Just match on the simple name of the annotation class.
I have 3 activities A, B, C.
When use @InjectExtra in activity B, then call it from activity A with Henson, all works good, but when adding some fields with @InjectExtra to activity C, it fails to generate code.
javax.annotation.processing.FilerException: Illegal name .ui.Henson
at com.sun.tools.javac.processing.JavacFiler.checkName(JavacFiler.java:495)
at com.sun.tools.javac.processing.JavacFiler.checkNameAndExistence(JavacFiler.java:517)
at com.sun.tools.javac.processing.JavacFiler.createSourceOrClassFile(JavacFiler.java:396)
at com.sun.tools.javac.processing.JavacFiler.createSourceFile(JavacFiler.java:378)
at com.f2prateek.dart.henson.processor.HensonExtraProcessor.process(HensonExtraProcessor.java:119)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:794)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:705)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91)
at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1035)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1176)
at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1173)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:859)
at com.sun.tools.javac.main.Main.compile(Main.java:523)
at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129)
at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138)
at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:45)
at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:33)
at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.delegateAndHandleErrors(NormalizingJavaCompiler.java:101)
at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:50)
at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:36)
at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:34)
at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:25)
at org.gradle.api.tasks.compile.JavaCompile.performCompilation(JavaCompile.java:157)
at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:139)
at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:93)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:243)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:219)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:230)
at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:208)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:203)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:185)
at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:62)
at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:50)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
I tried the rules as documented in README.md
but still get some warnings:
Warning: library class com.f2prateek.dart.henson.processor.HensonExtraProcessor extends or implements program class com.f2prateek.dart.common.AbstractDartProcessor
Warning: library class com.f2prateek.dart.henson.processor.HensonNavigatorGenerator extends or implements program class com.f2prateek.dart.common.BaseGenerator
Warning: library class com.f2prateek.dart.henson.processor.IntentBuilderGenerator extends or implements program class com.f2prateek.dart.common.BaseGenerator
Warning: library class com.f2prateek.dart.processor.ExtraInjectionGenerator extends or implements program class com.f2prateek.dart.common.BaseGenerator
Warning: library class com.f2prateek.dart.processor.InjectExtraProcessor extends or implements program class com.f2prateek.dart.common.AbstractDartProcessor
Warning: com.f2prateek.dart.common.AbstractDartProcessor: can't find referenced field 'javax.annotation.processing.ProcessingEnvironment processingEnv' in program class com.f2prateek.dart.common.AbstractDartProcessor
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.LinkOption
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Files
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Files
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Path
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.attribute.FileAttribute
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Files
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.OpenOption
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Files
Warning: com.squareup.javapoet.JavaFile: can't find referenced method 'java.nio.file.Path toPath()' in library class java.io.File
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Path
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Files
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Path
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Path
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Path
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Path
Warning: com.squareup.javapoet.JavaFile: can't find referenced class java.nio.file.Path
It creates a confusion : classes of annotation processing time are different from classes at runtime, it's generally a bad idea to refer to classes when creating an annotation processor.
And those dependencies will also slow down CI builds as the dep will always be downloaded when doing a clean build.
Had an idea for Dart that may be useful. On the Transfuse project we have a feature to build Activities using generated factories which enforce the required/optional characteristics of Extras (http://androidtransfuse.org/documentation.html#intent_factory). A similar feature would compliment Dart as it stands today, giving users a more structured way to start Activities (and possibly services):
class ExampleActivity extends Activity {
@InjectExtra("name") String name;
@InjectExtra("age") @Optinal Integer age;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
Dart.inject(this);
}
}
Generated factory generated by Dart every time @InjectExtra
is found per class:
DartFactory.start(new ExampleActivityFactory("Andy").setAge(42));
This is similar to the static factory approach taken by many people to start Activities/etc.
Just thought this would be neat. Let me know what you think.
In case we have
class C {
@InjectExtra String foo;
}
@Henson class B extends C {}
@Henson class A extends B {}
And naming is important (as A is processed before B), then A doesn't contain the extra injections of C.
I will submit a TDD bug fix in a few minutes.
So we can use ButterKnife or any other library's @Optional
annotation.
Mainly instances such as https://github.com/f2prateek/android-dart/blob/master/dart/src/main/java/com/f2prateek/dart/internal/ExtraInjector.java#L143. Saw a couple more in that file.
These might be done before v2 lands:
If I may suggest a new feature, it would be great to have the ability to inject extras without giving an 'extra key' the same way FragmentArgs works :
https://github.com/sockeqwe/fragmentargs
It generates default keys if none is given.
It would avoid boilerplate code when creating an intent such as intent.putExtra(SampleActivity.EXTRA_INT, anInt);
The intent could then be created like proposed in the issue :
#13
What do you think?
It'd be great to be able to use this with the Support lib versions of Fragments.
In order to use JavaPoet, we need to split the API and compiler artifacts into separate libraries.
From @stephanenicolas #33 (comment)
//the real intent builder would actually
//define a constructor with a context
//intent is a fake class below
public class IntentBuilder {
private Intent intent = new Intent();
//starting point
public AfterSettingA withA(A a) {
intent.putExtra(a);
return new AfterSettingA() {};
}
//intermediary states for mandatory fields
//one per field
public class AfterSettingA {
public AllSet withB(B b) {
intent.putExtra(b);
return new AllSet() {};
}
}
//final state, also allows to use optional fields.
public class AllSet {
//one method per optional field, same return type
public AllSet withC(C c) {
intent.putExtra(c);
return this;
}
//final method
public Intent build() {
return intent;
}
}
}
Hi, I am using proguard and faced with problem. Current dart proguard rules described here prevent from obfuscate class names.
I found similar issue for butterKnife and took solution from there.
JakeWharton/butterknife#117
Here is correct proguard rules:
-dontwarn com.f2prateek.dart.internal.**
-keep class **$$ExtraInjector { *; }
-keepclasseswithmembernames class * {
@com.f2prateek.dart.* <fields>;
}
When passing a List
or SparseArray
the generated $$IntentBuilder does not recognise the typed class as @Parcel
but only has support for classes marked with Parcelable
which works against of the strength of the Parcel-Dart2-Henson combination.
It is currently shipped to sonatype for every release. I remember there is a simple release.skip to insert in the pom to prevent that from happening..
Blocked by #45
Hi,
My "@InjectExtra" annotations doesn't work when I start my activity by launching "startActivityForResult". Is that a normal beavior?
Thanks,
Hi @f2prateek,
I am back to dart 2. Hioefully thus time i can finish pour migration..and we can release dart 2 + Henson.
There were some recent changes in AS. We don't need the apt plugin and scope anymore. If an artifact contains both the annotation processor and runtime code, Android Studio will work fine. The annotation processor can be proguarded and the generated code can be called from AS.
Thus I would change dart 2 to get artifacts with both runtime and annotation processor bundled together. One for Henson, one for dart. Maybe one parent also.
Is it fine for you ?
Both have the same name and are confusing users.
Hi,
I was about to start a similar project but just found yours. Thanks for the work. I built my app using Gradle and Proguard throws quite a lot of warnings. I thought it would be nice to have a proguard config in your README.md.
Hi,
I receive the following error when performing a lint check on my app :
InvalidPackage: Package not included in Android
../../../../../../../.gradle/caches/modules-2/files-2.1/com.f2prateek.dart/dart/1.1.0/cb3f00582e458cea0437f2ec9e04d30543ce7fb6/dart-1.1.0.jar: Invalid package reference in library; not included in Android: javax.lang.model.type. Referenced from com.f2prateek.dart.internal.ExtraInjector.
How can I solve this ?
Sometimes the generated Henson class (DSL) cannot be found when building/compiling the project.
It happens when another special compilation error occurs, but the Henson class shouldn't be affected...
A while ago we integrated Dart with Parceler. Recently FragmentArgs integrated as well and I wanted to point out their approach to the same problem. Specifically, FragmentArgs includes a "bundler
" parameter to specify any sort of adapter code necessary around reading/writing of bundle extras:
https://github.com/sockeqwe/fragmentargs#argsbundler
I imagine we could do the same thing and would reduce (the already low) coupling mainly around hard coding the Parcels.wrap
/unwrap
functionality. Something like this:
@InjectExtra(adapter = ParcelerAdapter.class) ParcelExample example;
Worth noting, Parceler allows you to identify beans to wrap outside of annotating them directly with @Parcel
. In the case where we define Parceler beans using @ParcelClass
(for instance), Dart would not wrap/unwrap the target instance.
I think it will make more sense if Dart use android.support.annotation.Nullable
instead of com.f2prateek.dart.Optional
The DSL doens't about the extra when parsing the intent, and there is no way to solve this at compile-time.
For now a workaround is to also declare the InjectExtra in the activity. But it looks fragile and the field could be removed during maintenance (needs a good comment).
Maybe we could introduce an annotation like @ForFragmentsOnly @InjectExtra String s
?
Hi guys,
First of all congratulations for the library, it is amazing!
I'd like to be added as a contributor to help you continue with this. Currently I'm working with Stephane on it.
Thanks!
What about the fact of making @HensonNavigable
to work, even if the Activity
contains @InjectExtra
annotated fields? In a similar way to @FragmentWithArgs
annotation from fragmentargs, this can allow to just annotate all activities, and then if extras are changed during the development cycle (even when all are removed) it still works.
Currently the CHANGELOG refers to a 1.2.0 WIP release that never seems to have been completed.
There is no CHANGELOG entry for the current 2.0.0 release.
Add a test case for when a class implements Parcelable and extends another class that implements a Parcelable.
class One implements Parcelable {
}
class Two extends One implements Parcelable {
}
The compile
dependency graph for dart looks like this:
build.gradle:
compile "com.f2prateek.dart:dart:2.0.0-RC1"
apt "com.f2prateek.dart:dart-processor:2.0.0-RC1"
./gradlew :app:dependencies:
{...}
+--- com.f2prateek.dart:dart:2.0.0-RC1
| +--- com.f2prateek.dart:dart-annotations:2.0.0-RC1
| | \--- com.squareup:javapoet:1.0.0
| \--- com.squareup:javapoet:1.0.0
Both the dart-annotations
and dart-common
modules include a dependency on com.squareup:javapoet
, which isn't needed (as far as I can tell from scanning the source code). Please consider leaving this dependency in only for the processor portion of the library.
EDIT: As a quick fix, one can exclude javapoet explicitly for the compile artifact:
compile ("com.f2prateek.dart:dart:2.0.0-RC1") {
exclude group: "com.squareup", module: "javapoet"
}
apt "com.f2prateek.dart:dart-processor:2.0.0-RC1"
Would this be something you are interested in adding to the library? It would be great to create intents across the app in a consistent manner.
When an activity defines its extras in an external class and injects them through Dart.inject(Object target, Activity source)
, Henson cannot find them.
A possible solution may be a new annotation. It could be used to do two things:
For instance: @InjectExtraTarget private Model model = new Model();
...
As I try to integrate Henson into my app I get a lengthy list of errors that follow this format: error: Unable to write henson navigator for types SomeAbstractClass: Illegal name .Henson
.
I notice there's a unit test in place which says that Henson should ignore abstract classes in com.f2prateek.dart.henson.processor.HensonNavigatorGeneratorTest#henson_doesntGotoAbstractClasses
but it doesn't seem to be the case if there's an @InjectExtra annotation. Is there a way to work around this?
It is confusing as Henson is also the name of the generated class.
It would be nice, in order to remain semantically consistent, to have a specific annotation for Fragment's Arguments.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.