Skip to content
This repository was archived by the owner on Feb 26, 2023. It is now read-only.
This repository was archived by the owner on Feb 26, 2023. It is now read-only.

Use a factory for enhanced classes #1017

Description

@PerfectCarl

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, @App annotations.

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

    startActivity(new Intent(this, MyActivity_.class)); 
    // Or better (thanks @WonderCsabo)
        MyActivity_.intent(this).start() ;

Two. when you use an EGroupView in your ListAdapter

@EBean
public class FavoriteAdapter extends BaseAdapter {

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        FavoriteItemView view;
        if (convertView == null) {
            view = FavoriteItemView_.build(context);
        } else {
            view = (FavoriteItemView) convertView;
        }
        view.bind(getItem(position));
        return view;
    }
}

or a EFragment in a PagerAdapter

public class MyAdapter extends FragmentStatePagerAdapter  {

    @Override
    public Fragment getItem(int position) {
        return ImageFragment.newInstance(resolver, position,
                quelle.getImage(position), settings);
    }
}
@EFragment(R.layout.image_fragment)
public class ImageFragment extends FragmentBase {
    public static ImageFragment newInstance(UilResolver resolver, int num,
            Image image, ImageSettings settings) {
        ImageFragment fragment = new ImageFragment_();

        return fragment;
    }
}

Three. when you use preferences

    @Pref
    MyPrefs_ prefs;

Four. when you want to use EBean in non enhanced classes

public class Utils{
    void doSomething(){
        MyBean bean = MyBean_.getInstance_(context) ;
    }
}

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 (or CreatorFacade as @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:

    startActivity(new Intent(this, Factory.getClass(MyActivity.class)));

    MyBean bean = Factory.create(MyBean.class, context) ;
    FavoriteItemView view = Factory.create(FavoriteItemView.class, context);
    ImageFragment view = Factory.create(ImageFragment.class, context);

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.

Activity

  1. WonderCsabo commented on May 30, 2014

    @WonderCsabo
    Member

    Please note that @Pref is another issue: the methods in the interface and the generated class are not matching (PrefField vs. 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 AAs IntentBulder do that.

  2. WonderCsabo commented on May 30, 2014

    @WonderCsabo
    Member

    I agree. I really do not like to use generated class names in my code. You are right about that EFragment, EView and EGroupView are also suffering from this issue. @DayS, @yDelouis WDYT?

  3. yDelouis commented on May 30, 2014

    @yDelouis
    Contributor

    Comments

    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.
    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 the Factory class.

    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 EActivity and EBean but 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 EBeanCreator is 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 Creator and Factory. I prefer Factory because it seems more generic. But, I put Creator in the examples because these interfaces look more like the Creator class of PerfectCarl's proposition.

  4. PerfectCarl commented on May 31, 2014

    @PerfectCarl
    ContributorAuthor

    @yDelouis
    I like your proposition but the case four is left behind

    public 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 the CreatorFacade, 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 class

    public interface ECreator<T> {
        T create();
        // Returns MyBean_.class
        class<?> getEnhancedClass() ; 
    }
  5. PerfectCarl commented on May 31, 2014

    @PerfectCarl
    ContributorAuthor

    @WonderCsabo

    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 IntentBuilder

  6. WonderCsabo commented on May 31, 2014

    @WonderCsabo
    Member

    Then, 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

  7. yDelouis commented on May 31, 2014

    @yDelouis
    Contributor

    @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.

    @PerfectCarl,

    I like your proposition but the case four is left behind

    For this case, you could inject a creator in the custom Application and 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 Utils with @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 Application and that Activities, Fragments or Services depend 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 ActivityCreator would have a method which returns an ActivityIntentBuilder (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, the Creator would return the instance directly but for Activity, it would return an ActivityIntentBuilder and for Fragment, a FragmentBuilder.

    Besides, I would suggest to add a method to retrieve the class

    +1

  8. PerfectCarl commented on May 31, 2014

    @PerfectCarl
    ContributorAuthor

    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 EActivityCreator returns the generic ActivityIntentBuilder instead of the MyActivityIntentBuilder_ it might be convient to add a ActivityIntentBuilder.start(Bundle) to provide extra data to the intent in a generic (albeit not type safe) way.

  9. PerfectCarl commented on Jun 1, 2014

    @PerfectCarl
    ContributorAuthor

    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 Factory based on the enhanced application.

    Have MyApplication_ implements FactoryApplication

    public 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...

  10. gauravsak commented on Dec 30, 2016

    @gauravsak

    @WonderCsabo @PerfectCarl @yDelouis What is the current status of this issue? There hasn't been an update for a while. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions