Repository navigation
Use a factory for enhanced classes #1017
Description
Activity
Please note that
@Prefis another issue: the methods in the interface and the generated class are not matching (PrefFieldvs. the relevant Java type) hence the generated class cannot implement the interface. But there is a plan to change this.Also we should not create
Intents, let AAsIntentBulderdo that.Comments
To register
Creators at application startup, we need to add an annotation on theApplicationclass for each enhanced class, which I don't like so much.
Then, I don't like to put data in static attributes because they can be garbage collected if memory is needed. And if we put the creators in the application instance, we won't be able to retrieve it from theFactoryclass.Another proposition
The developer would write something like this :
@EActivity public class MyActivity extends Activity { @Creator protected EBeanCreator<MyBean> myBeanCreator; public MyBean createNewEnhancedMyBean() { return myBeanCreator.create(); } } @EBean public class MyBean { }
The example is with
EActivityandEBeanbut will work with all enhanced class.
And the generated code will look like this :public class MyActivity_ extends Activity { private void init_(Bundle savedInstanceState) { ... myBeanCreator = new MyBean_.Creator_(this); ... } } public class MyBean_ extends MyBean { ... public static class Creator_ implements EBeanCreator<MyBean> { private Context context; public Creator_(Context context) { this.context = context; } public MyBean_ create() { return MyBean_.getInstance(context); } } }
And
EBeanCreatoris an interface present in the API jar :public interface EBeanCreator<T> { T create(); }
Some other interfaces would exist for
Activities,Services,Fragments,Views, etc...About the naming, I hesitate between
CreatorandFactory. I preferFactorybecause it seems more generic. But, I putCreatorin the examples because these interfaces look more like theCreatorclass of PerfectCarl's proposition.@yDelouis
I like your proposition but the case four is left behindpublic class Utils{ void doSomething(){ MyBean bean = MyBean_.getInstance_(context) ; } }
Maybe it's not used that often( I personally don't) but it was the bread and butter (as far as I understood) of the original contribution.
To register Creators at application startup, we need to add an annotation on the Application
class for each enhanced class, which I don't like so much.I, too, don't like it very much.
Note that you can also do this:@ECreator() public interface MyCreator extends Creator { @Creates(MyBean.class) MyBean createMyBean(Context context); @Creates(MyBetterBean.class) MyBetterBean createMyBetterBean(Context context); }
So there will be only one Creator in your whole application.
Still you have to register all the enhanced components that you are willing to create via theCreatorFacade, but it feels prettier.As a rule, I resent that apt doen't let us register somehow a bunch of interesting/annotated classes after the code generation is done.
Your solution deals with that elegantly and clearly states the dependency between your class and the enhanced component.
Then, I don't like to put data in static attributes because they can be garbage collected
if memory is needed.Good point.
And if we put the creators in the application instance, we won't be able to retrieve it from
the Factory class.What do you mean?
Last point, how would you create intents for your EActivity ?
Is there a need for Activity, Fragment, Bean ... specific interface?
Besides, I would suggest to add a method to retreive the classpublic interface ECreator<T> { T create(); // Returns MyBean_.class class<?> getEnhancedClass() ; }
Please note that @PREF is another issue:
Agreed but I aimed at being comprehensive. Listing cases that would be excluded from that suggestion (like case 3 and 5)
Thanks for the reminder about
IntentBuilderThen, I don't like to put data in static attributes because they can be garbage collected
if memory is needed.I think he wanted to write:
because they cannot be garbage collected
@WonderCsabo, I wanted to write what I wrote. I meant that static attributes can be reset if the application is killed. And even if it will be initialized again when the application restarts, I think it's not a good pattern in Android to have static singletons.
I like your proposition but the case four is left behind
For this case, you could inject a creator in the custom
Applicationand write something like this :public class Utils { void doSomething(Context context) { MyApplication app = (MyApplication) context.getApplicationContext(); MyBean myBean = app.creator.create(); ... } }
But it seems a bit strange to me to write this because you'd just have to annotate your class
Utilswith@EBean.So there will be only one Creator in your whole application.
Still you have to register all the enhanced components that you are willing to create via the CreatorFacade, but it feels prettier.I don't like to put too much things in the
Applicationand thatActivities,FragmentsorServicesdepend on the application object. I like to keep things small and that classes does not know more than the things they are responsible for. A bean shouldn't know the application in which it lives, a view shouldn't know about the activity is embed in.As a rule, I resent that apt doesn't let us register somehow a bunch of interesting/annotated classes after the code generation is done.
Annotation processing works this way to enable incremental build. And there is no way around this.
And if we put the creators in the application instance, we won't be able to retrieve it from
the Factory class.What do you mean?
In the
Factory, you'll have to write something like this :public class Factory { public <T> T create(Class<T> clazz, Context context) { MyApplication app = (MyApplication) context.getApplicationContext(); return app.create(clazz); } }
But this class will be in the annotation api jar and won't know what class to cast applicationContext to.
Last point, how would you create intents for your EActivity ?
The interface
ActivityCreatorwould have a method which returns anActivityIntentBuilder(added by PR #994). We still have a problem with extras. This comment could help us.Is there a need for Activity, Fragment, Bean ... specific interface?
The answer is given above. For
Bean, theCreatorwould return the instance directly but forActivity, it would return anActivityIntentBuilderand forFragment, aFragmentBuilder.Besides, I would suggest to add a method to retrieve the class
+1
I don't like to put too much things in the Application and that Activities, Fragments or Services depend on the application object. ...
Me neither. You are making a very strong point here.
The interface ActivityCreator would have a method which returns an ActivityIntentBuilder
...
The answer is given above. For Bean, the Creator would return the instance directly but for Activity, it would return an ActivityIntentBuilder and for Fragment, a FragmentBuilder.Using the builder classes, the proposed interfaces could be (names are just for information):
public interface EBeanCreator<T> { T create(); // Returns MyBean_.class class<?> getEnhancedClass() ; } public interface EActivityCreator<T> { T create(); class<?> getEnhancedClass() ; ActivityIntentBuilder<?????> intent() ; }
If the interface
EActivityCreatorreturns the genericActivityIntentBuilderinstead of theMyActivityIntentBuilder_it might be convient to add aActivityIntentBuilder.start(Bundle)to provide extra data to the intent in a generic (albeit not type safe) way.I was re-reading
In the Factory, you'll have to write something like this :
public class Factory { public <T> T create(Class<T> clazz, Context context) { MyApplication app = (MyApplication) context.getApplicationContext(); return app.create(clazz); } }
But this class will be in the annotation api jar and won't know what class to cast applicationContext to.
Well to be fair there is a way to implement the
Factorybased on the enhanced application.Have
MyApplication_implementsFactoryApplicationpublic interface FactoryApplication<T> { <T> T create(Class<T> clazz) ; }
and write the following code then
public class Factory { public <T> T create(Class<T> clazz, Context context) { FactoryApplication app = (FactoryApplication) context.getApplicationContext(); return app.create(clazz); } }
Then we can have case four, not rely on static fields.
Of course, we'd still use the Application as the singleton that it is, but this is just an implementation detail...@WonderCsabo @PerfectCarl @yDelouis What is the current status of this issue? There hasn't been an update for a while. Thanks!
As much as we love using AA, we don't like referencing enhanced classes (or _classes) in our code.
First because it's not architecturally pleasing.
And second because when the AA generation fails, you have to remove all the imports that reference the enhanced classes before AA generation can start again.
Which is always a pain.
Referencing the enhanced class
The truth is AA helps to avoid referencing the enhanced components via the
@Bean,@FragmentById,@Appannotations.Nevertheless, there are still use cases when you have to invoke enhancing classes in your code (and have an added import clause):
One. when you start an EActivity
Two. when you use an EGroupView in your ListAdapter
or a EFragment in a PagerAdapter
Three. when you use preferences
Four. when you want to use EBean in non enhanced classes
Five. In your xml layout, if you use EFragment
Well, let's say that there is nothing to be done for three and five, but for the other cases...
Introducing a factory
This proposition is heavily based on the work of @jeremiemartinez and @fredszaq that they released on their branch.
The idea is to create a new class
Factory(orCreatorFacadeas @jeremiemartinez named it) in androidannotations-api.jar.Creator_ classes will be generated for each enhanced classes and will register at the application startup to the
Factory.addCreator().The factory will be available everywhere in the code:
Next steps
Well, as I said @jeremiemartinez and @fredszaq laid out the path for us. Their branch work and demonstrate the whole idea.
For the moment, it works only EBean. EFragment, EView and EGroupView should be added.
And the best thing is that that solution doesn't use reflection. At all.