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.

@AfterViews method is called multiple times if the same @EFragment instance is added again #1264

Closed

Description

@litao0621

the first to enter called once ,the second to enter called twice,and so on

This is my code

public void beginTransaction(Fragment fragment, String tag, Bundle bundle) {
        try {

            manager = getFragmentManager();
            if (bundle != null) {
                fragment.setArguments(bundle);
            }
            transaction = manager.beginTransaction();
            transaction.replace(R.id.main, fragment, tag);

            transaction.commit();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @CheckedChange({ R.id.main_tab_project, R.id.main_tab_moment,R.id.main_tab_infomation,R.id.main_tab_personal_info })
    void checkedChangedOnSomeCheckBoxs(CompoundButton btn, boolean isChecked) {
        if (isChecked) {
        switch (btn.getId()) {
        case R.id.main_tab_project:
            beginTransaction(mFragmentProjectList,"mFragmentProjectList",null);
            break;
        case R.id.main_tab_moment:
            beginTransaction(mFragmentMoment,"mFragmentMoment",null);
            break;
        case R.id.main_tab_infomation:
            beginTransaction(mFragmentInformation,"mFragmentInformation",null);
            break;
        case R.id.main_tab_personal_info:
            beginTransaction(mFragmentPersonal,"mFragmentPersonal",null);
            break;
        }
        }
    }
<RadioGroup
            android:id="@+id/main_radio"
            android:layout_width="match_parent"
            android:layout_height="@dimen/comm_line_height"
            android:layout_alignParentBottom="true"
            android:padding="@dimen/comm_space_3"
            android:gravity="bottom"
            android:background="@color/header_background_color"
            android:orientation="horizontal" >

            <RadioButton
                android:id="@+id/main_tab_project"
                style="@style/MainTabTheme"
                android:drawableTop="@drawable/selector_main_tab_project"
                android:text="@string/main_tab_project" />
            <RadioButton
                android:id="@+id/main_tab_moment"
                style="@style/MainTabTheme"
                android:drawableTop="@drawable/selector_main_tab_project"
                android:text="@string/main_tab_moment" />

            <RadioButton
                android:id="@+id/main_tab_infomation"
                style="@style/MainTabTheme"
                android:drawableTop="@drawable/selector_main_tab_information"
                android:text="@string/main_tab_information" />

            <RadioButton
                android:id="@+id/main_tab_personal_info"
                style="@style/MainTabTheme"
                android:drawableTop="@drawable/selector_main_tab_personal_info"
                android:text="@string/main_tab_personal_info" />
        </RadioGroup>

Activity

  1. WonderCsabo commented on Dec 2, 2014

    @WonderCsabo
    Member

    There is no @AfterViews in the code snippet above.

  2. litao0621 commented on Dec 3, 2014

    @litao0621
    Author

    The above code is Activity code,

    My activity has four radio buttons.Each radio button can enter different fragments.As the above code
    my English is poor. I want you to understand that

    This is a Fragment code

    @EFragment(R.layout.fragment_moment)
    public class MomentFragment extends BaseFragment implements OnRefreshListener2<ListView> {
    
    private final static int DURATION = 1 * 500;
    
        @ViewById(R.id.back)
        TextView mTextViewBack;
        @ViewById(R.id.top_right_icon)
        ImageView mImageViewTopRight;
        @ViewById(R.id.pointer)
        TextView mTextViewPointer;
        @ViewById(R.id.radiogroup_tab)
        RadioGroup mRadioGroup;
        @ViewById(R.id.moment_topic_recommend)
        RadioButton mRadioButtonRecommend;
        @ViewById(R.id.moment_project)
        RadioButton mRadioButtonProject;
        @ViewById(R.id.moment_demo)
        RadioButton mRadioButtonDemo;
        @ViewById(R.id.vPager)
        ViewPager mViewPager;
    
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            reset();
            return null;
        }
    
        @AfterViews
        public void init() {
    
                initTitle();
                initViewPager();
                mRadioButtonRecommend.performClick();
    
    
        }
  3. AhmedAsser commented on Dec 3, 2014

    @AhmedAsser

    i have this problem too,when re-replacing the same fragment the onViewChanged() function inside the generated class called several times for the same fragment .
    so if i re replace Fragment_x ( lets say 5 times ) then on the sixth replacing my @AfterView function called and executed 6 times for the same fragment A_Fragment_.

  4. WonderCsabo commented on Dec 3, 2014

    @WonderCsabo
    Member

    In case of @EFragments the @AfterViews annotated methods are called whenever the onViewCreated method gets called on the Fragment. I think this is correct. If you have problems, put a breakpoint in your @AfterViews annotated method and check when they are called.

  5. AhmedAsser commented on Dec 3, 2014

    @AhmedAsser

    The onViewCreated and all others method are called once but the problem @Afterviews annotated methods are called (n) number of times and that number depends on how many times you replaced that EFragment.

    when you call: transaction.replace(R.id.main, fragment, tag); for the sixth time:

    onCreate() called once.
    onCreateView() called once.
    onViewCreated () called once.
    @Afterviews MyFunction() get called 7 times.
    onResum() called once.

  6. WonderCsabo commented on Dec 3, 2014

    @WonderCsabo
    Member

    I cannot really follow you. If onCreateView() is called once, how can your annotated method be called multiple times? Since onCreateView() calls your method once, and onCreateView() is called once, your method will be called just once. This is only true for one instance of the @EFragment of course.

  7. AhmedAsser commented on Dec 3, 2014

    @AhmedAsser

    Not sure.. but i think every time re replacing same instance fragment the generated EFragment class call onCreate() which call init_ which call OnViewChangedNotifier.registerOnViewChangedListener(this) and add same (this) fragment again to list of listeners.

    then onCreateView() call onViewChangedNotifier_.notifyViewChanged(this) which loop for all registered liteners for the same fragment and call onViewChanged() multiple times that call @AfterView method.
    !!

  8. WonderCsabo commented on Dec 4, 2014

    @WonderCsabo
    Member

    Can i ask one of you to create and share a minimal test project which can reproduce this issue? Thanks.

  9. AhmedAsser commented on Dec 7, 2014

    @AhmedAsser

    https://github.com/AhmedAsser/AndroidannotationsTestIssue1264
    This is a minimal project that shows how this issue occurs,you can toggle between new fragment and one instance of fragment @efragment within activity.

    debugging_list
    this image from OnViewChangedNotifier class which describes the cause of this problem.

    public class OnViewChangedNotifier {
    
        private static OnViewChangedNotifier currentNotifier;
    
        public static OnViewChangedNotifier replaceNotifier(OnViewChangedNotifier notifier) {
            OnViewChangedNotifier previousNotifier = currentNotifier;
            currentNotifier = notifier;
            return previousNotifier;
        }
    
    
        public static void registerOnViewChangedListener(OnViewChangedListener listener) {
            if (currentNotifier != null) {
                currentNotifier.listeners.add(listener);
            }
        }
    
        private final List<OnViewChangedListener> listeners = new LinkedList<OnViewChangedListener>();
    
        public void notifyViewChanged(HasViews hasViews) {
            for (OnViewChangedListener listener : listeners) {
                listener.onViewChanged(hasViews);
            }
        }
    
    }

    registerOnViewChangedListener function keeps adding same instance of fragment to list of listeners ,may be using LinkedHashSet solves this problem.
    thanks.

  10. WonderCsabo commented on Dec 7, 2014

    @WonderCsabo
    Member

    @AhmedAsser thanks for the detailed report, this makes identifying and fixing issues much more easier. I'll take a look at this tomorrow evening.

  11. yoavst commented on Dec 9, 2014

    @yoavst

    any updates? happens to me too

  12. WonderCsabo commented on Dec 9, 2014

    @WonderCsabo
    Member

    Sorry, i did not have time to check this. I'll update this thread if i have news on the issue.

  13. self-assigned this
    on Dec 9, 2014
  14. WonderCsabo commented on Dec 9, 2014

    @WonderCsabo
    Member

    @AhmedAsser you should not include dependencies in the lib folder of your project, instead download them from a repository at build time with dependency management. Check out our gradle instructions. (I had a strange error because of this, that's why i noticed that.)

  15. WonderCsabo commented on Dec 9, 2014

    @WonderCsabo
    Member

    @AhmedAsser this is what your test project does:

    @CheckedChange(R.id.toggle)
    protected void toggleClick(CompoundButton buttonView, boolean isChecked){
      if(!mFragmentInstance.isVisible()){
        getSupportFragmentManager().beginTransaction().replace(R.id.container, mFragmentInstance).commit();
      } else{
        getSupportFragmentManager().beginTransaction().replace(R.id.container, FragmentTest_.builder().mType("Just New Fragment").build()).commit();
      }
    }
    1. click: mFragmentInstance is not visible, so it adds it
    2. click: mFragmentInstance is visible, it is removed and another instance is added
    3. click. mFragmentInstance is not visible, so the another instance is removed and mFragmentInstance is added again

    And it already has an OnViewChangedNotifier with one listener, and in this case we add another listener so that is why @AfterViews is called twice.

    So basically the problem is: your are adding again the same Fragment instance.

    I am not really sure this is a good practice. Did you intentionally write the code this way?

  16. 6 remaining items

  17. WonderCsabo commented on Dec 9, 2014

    @WonderCsabo
    Member

    I totally agree with you. But i can add exact reasons:

    • if you retain a Fragment instance, you will hold onto all of the objects owned by it. This means all data and Views, which are very costly objects, but those will not be available for GC, so you are "leaking" memory (at least for a while).
    • re-adding the same instance is against the Fragment lifecycle, which goes from the starting point again. onAttach, onCreate etc will be called again on the same instance, which is totally against the lifecycle model. For example the contract of onCreate is called to do initial creation of the fragment.
    • there are good ways to restore the state of a Fragment like onSaveinstanceState which will create a light-weight Bundle and then you can reconstruct the Fragment from it.
    • or you can put the Fragment into the back stack.
  18. AhmedAsser commented on Dec 10, 2014

    @AhmedAsser

    @WonderCsabo , I am aware of what you said ,but there are some cases that i really want to reuse the same instance of the fragment ,especially when it holds a significant amount of data like arrays or views without using bundle ,re inflating or making load on proccessor every time a new instance is created.or get a fragment by findFragmentByTag() and reuse it with another view later .
    Also I thing AA was built to speed up the development, not to force developer the way he should take to build his code.I want to use EFragment annotaion in all cases without any problems.
    Why not just prevent this issue from happening and let the developer to decide if he wants to use the same instance or not without any problems?

  19. WonderCsabo commented on Dec 10, 2014

    @WonderCsabo
    Member

    @AhmedAsser if you reuse the Fragment instance, all View inflation, injection etc. will happen again, because the system will call onCreateView and co. on the same instance again. So you will not spare loading at all. Of course we could add a alreadyInitalized flag to everywhere and check for that , but that would require a lot of change in annotation handlers, also would make a code less readable for a bad use-case...

  20. AhmedAsser commented on Dec 10, 2014

    @AhmedAsser

    Of course i can check if my data or views had already Initialized or not when one of onCreate() or onCreateView() getting called again, so i can prevent previous data and view from re-inflating and re-initialized again and that is why i reused it,also I think even if EFragment is reused again AA should check if this fragment already registered or not to prevent this weird behavior Unless you did that intentionally for some useful reasons.
    if a developer used navigation drawer and he does not aware of this issue ,if he exchanged between main Efragment and other menu fragment many times, findViewById() and @AfterView within onViewChange() would call so many times.and he will blame AA for that because without it things seem to go normal!
    Anyway i will find a way to handle this case.
    Thanks.

  21. WonderCsabo commented on Dec 10, 2014

    @WonderCsabo
    Member

    So you know saying that we should prevent reinitializing. But first you asked that afterViews should be called again, but only once. ;)

  22. dodgex commented on Dec 10, 2014

    @dodgex
    Member

    Also I thing AA was built to speed up the development, not to force developer the way he should take to build his code.I want to use EFragment annotaion in all cases without any problems.

    Yes, AA is built to speed up development, but that does not mean that we have to add some stuff to support usage of bad coding practices.

    BUT: there is a feature that might help you, at least partially. You could use the @InstanceState annotation to store the data you do not want to "lose" when getting a new instance.

    that said, i think we should not to support this use case. but the final decision is up to @WonderCsabo and the other collaborators.

  23. AhmedAsser commented on Dec 11, 2014

    @AhmedAsser

    but that does not mean that we have to add some stuff to support usage of bad coding practices.

    Sorry, my English is not good,I did not mean that ,what i meant is that if this use case could happen such as many cases (regardless if it is bad or not),why not just make AA handle it in proper way rather than make things worse ,and developer can decide later to use this case or not according to his needs.
    And I really do not know why you supposed that if you prevent it from happen then you would support a bad coding practice! ,even if this case looks like a bad practice i think it is a bad idea to ignore it .

    i think we should not to support this use case.

    I am not asking for making new annotation or adding additional support .In fact, this case works properly without EFragment ,but the AA has an Unexpected behavior toward this case

    Also, if @afterView is programed to get called just once for every new instance then why you are coding it in a way that allow EFragment to register itself more than once?

    public static void registerOnViewChangedListener(OnViewChangedListener listener) {
        if (currentNotifier != null) {
            currentNotifier.listeners.add(listener);
        }
    }
    

    Does using new OnViewChangedNotifier or clearing listeners or even using LinkedHashSet affect the normal behavior of EFragment ?

  24. WonderCsabo commented on Dec 11, 2014

    @WonderCsabo
    Member

    No, these do not effect the normal behavior. However, you want to just @AfterViews to be called once. But if we kinda support this, others may want the @EFragment not to be initialized again. I am not sure we would want to go this road and support this.
    Please note AA always covers the most common use cases, for others special things you have to go the old Android way.

    But i am really interested in @DayS and @yDelouis thoughts.

  25. dodgex commented on Dec 11, 2014

    @dodgex
    Member

    i think i got what @AhmedAsser wants to say. and the more i think about it, i think it makes sense.

    sure it is bad practice to reuse a fragment, but as it is possible, we should avoid the behaviour that the @AfterView is called multiple times. and it seems (just thinking about it not checking/testing it) that the suggestion with using a set vs a list (or at least checking contains before adding) could avoid/fix the issue of multiple calls after views have been set.

    BUT @AhmedAsser that would still mean, that the @AfterViews would be called after each (re-)add as it is part of the lifecycle. if you don't want this, you have to use a boolean isInitialized in your code or do not use @EFragment for that case.

  26. WonderCsabo commented on Dec 11, 2014

    @WonderCsabo
    Member

    @dodgex i think i understood @AhmedAsser.

    My point is: we can try to "fix" this case, but since it is a bad design initially, it is hard to define what would be the good behavior. For @AhmedAsser his proposal fits for now, but for others or other apps another workaround would be "better". So that's why i think we should not "fix" this at all.

  27. yDelouis commented on Dec 12, 2014

    @yDelouis
    Contributor

    Here is my thought :
    I don't like the way AA manage views injection. I think a view should be injected only if it belongs the enhanced class where it is injected. For example, a view in a fragment should be injected only if view_.findViewById(...) return something. If the view is in the activity where the fragment is, it should not be injected.
    To be more clear, I would remove all things related to OnViewChangedNotifier and HasViews.
    It would break the API because a EBean won't be able have injected views. But having a view in a EBean is a bad idea I think because a EBean can be a singleton and it would leak all the Context.

    About this issue, the proposal to change the List by a Set seems good to me.

  28. dodgex commented on Dec 13, 2014

    @dodgex
    Member

    "fixed" with PR #1275

    screen

  29. litao0621 commented on Dec 14, 2014

    @litao0621
    Author

    Nice one.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