0

I have a custom view that performs some network operations. From that operation result the view builds UI.

The view contains a list of cards that are fetched via internet. This view is used in multiple places. Lets say one of them is my fragment.

Here's what I do inside of fragment:

class MyFragment extends Fragment  {
    // same as findViewById
    @BindView(R.id.card_list) CardHelper cardHelper;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // init my element with some data before it initializes
        cardHelper.init(data);
    }
}

Here's how my custom view looks like:

public class CardHelper extends ListView { 

   // some info that is built from internet data
   private List<HashMap<String, String>> cardElements = new ArrayList<>();

    public void init(Data data) {
        // does async network and then build a view based on results
        // Network operations could depend on data param
    }
}

OK, now when user switches from my app to another one, my Fragment could be destroyed. And I wanna saved the state of my custom view. Thus my view doesn't need to get information from internet again. So I append my CardHelper with the following data (based on this answer):

public class CardHelper extends ListView { 
   // some info that is built from internet data
   private List<HashMap<String, String>> cardElements = new ArrayList<>();

    public void init(Data data) {
        // does async network and then build a view based on results
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.cardElements = this.cardElements;
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if(state instanceof SavedState) {
            SavedState ss = (SavedState)state;
            super.onRestoreInstanceState(ss.getSuperState());
            this.cardElements = ss.cardElements;
        } else {
            super.onRestoreInstanceState(state);
        }
    }

    private static class SavedState extends BaseSavedState {
        private List<HashMap<String, String>> cardElements = new ArrayList<>();

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            this.cardElements = new ArrayList<>();
            in.readList(this.cardElements, null);
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeList(this.cardElements);
        }

        public static final Parcelable.Creator<SavedState> CREATOR =
                new Parcelable.Creator<SavedState>() {
                    public SavedState createFromParcel(Parcel in) {
                        return new SavedState(in);
                    }

                    public SavedState[] newArray(int size) {
                        return new SavedState[size];
                    }
                };
    }
}

For the sake of argument, I wanna save the view's data from this view itself. So fragments/activities or w/e use it wouldn't need to know anything how it works inside. And if I use it from multiple places I wouldn't need to duplicate code that stores\restores this view.

Ok, to the problem now:

When CardHelper is created the very first time I perform init to initialize the view manually with some data. When my fragment gets destroyed and app goes to background and then gets restored, methods are executed in the following priority:

  1. SavedState: private SavedState(Parcel in) {
  2. SavedState: public void writeToParcel(Parcel out, int flags) {
  3. CardHelper: public void init(Data data) {
  4. CardHelper: public void onRestoreInstanceState(Parcelable state) {

As you see onRestoreInstanceState executed only after onActivityCreated. But I need to know if I should perform network operation in init method before that. So I want to onRestoreInstanceState be executed first and only then init. But there're no method in fragments LifeCycle that could be done after this.

How to I reformat my code that I can call some operation within my fragment after onRestoreInstanceState on CardHelper gets called? One more thing that onRestoreInstanceState wouldn't be called for the first time when CardHelper is created. But I need to initialize my CardHelper in this case as well.

Community
  • 1
  • 1
deathangel908
  • 8,601
  • 8
  • 47
  • 81

1 Answers1

1

The easy fix should be posting the init() step after cardHelper has been laid out, therefore onRestoreInstanceState has already been called. To do that you just have to perform init() in post(...):

cardHelper.post(new Runnable() {
                   public void run() {
                       cardHelper.init();
                   }
               });

Haven't tested it, I assume it will work.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • Yep, this works. Probably there's a more elegant solution out there. – deathangel908 Feb 18 '17 at 11:35
  • The problem in your code is, that `View` is aware of business logic. Normally, it should NOT happen so. The elegant solution would be not to save the data in view itself, consider changing your architecture. – azizbekian Feb 18 '17 at 11:37
  • Could you please suggest how it's should be done? The idea was to encapsulate logic to view itself. So people who use my view wouldn't need to worry about anything just past the view to `layout.xml`. If I move logic away from my view, user should to be aware that he MUST save bundle manually. – deathangel908 Feb 18 '17 at 11:49
  • If you have such a use case, a better solution would be to perform `post()` in your View yourself and not to rely on the client to perform it on his behalf. Client will call normal `init` and you will `post` init logic within your view. – azizbekian Feb 18 '17 at 11:59
  • Still, you may have issues in Nougat, which introduced limit to saving data in Bundle. You will get an exception when trying to save too much data in bundle. – azizbekian Feb 18 '17 at 12:07