/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.app; import android.content.Context; import android.os.Bundle; /** * This fragment watches its primary lifecycle events and throws IllegalStateException * if any of them are called out of order or from a bad/unexpected state. */ public class StrictFragment extends Fragment { public static final int DETACHED = 0; public static final int ATTACHED = 1; public static final int CREATED = 2; public static final int ACTIVITY_CREATED = 3; public static final int STARTED = 4; public static final int RESUMED = 5; int mState; boolean mCalledOnAttach, mCalledOnCreate, mCalledOnActivityCreated, mCalledOnStart, mCalledOnResume, mCalledOnSaveInstanceState, mCalledOnPause, mCalledOnStop, mCalledOnDestroy, mCalledOnDetach, mCalledOnAttachFragment; static String stateToString(int state) { switch (state) { case DETACHED: return "DETACHED"; case ATTACHED: return "ATTACHED"; case CREATED: return "CREATED"; case ACTIVITY_CREATED: return "ACTIVITY_CREATED"; case STARTED: return "STARTED"; case RESUMED: return "RESUMED"; } return "(unknown " + state + ")"; } public void onStateChanged(int fromState) { checkGetActivity(); } public void checkGetActivity() { if (getActivity() == null) { throw new IllegalStateException("getActivity() returned null at unexpected time"); } } public void checkState(String caller, int... expected) { if (expected == null || expected.length == 0) { throw new IllegalArgumentException("must supply at least one expected state"); } for (int expect : expected) { if (mState == expect) { return; } } final StringBuilder expectString = new StringBuilder(stateToString(expected[0])); for (int i = 1; i < expected.length; i++) { expectString.append(" or ").append(stateToString(expected[i])); } throw new IllegalStateException(caller + " called while fragment was " + stateToString(mState) + "; expected " + expectString.toString()); } public void checkStateAtLeast(String caller, int minState) { if (mState < minState) { throw new IllegalStateException(caller + " called while fragment was " + stateToString(mState) + "; expected at least " + stateToString(minState)); } } @Override public void onAttachFragment(Fragment childFragment) { mCalledOnAttachFragment = true; } @Override public void onAttach(Context context) { super.onAttach(context); mCalledOnAttach = true; checkState("onAttach", DETACHED); mState = ATTACHED; onStateChanged(DETACHED); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (mCalledOnCreate) { throw new IllegalStateException("onCreate called more than once"); } mCalledOnCreate = true; checkState("onCreate", ATTACHED); mState = CREATED; onStateChanged(ATTACHED); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mCalledOnActivityCreated = true; checkState("onActivityCreated", ATTACHED, CREATED); int fromState = mState; mState = ACTIVITY_CREATED; onStateChanged(fromState); } @Override public void onStart() { super.onStart(); mCalledOnStart = true; checkState("onStart", ACTIVITY_CREATED); mState = STARTED; onStateChanged(ACTIVITY_CREATED); } @Override public void onResume() { super.onResume(); mCalledOnResume = true; checkState("onResume", STARTED); mState = RESUMED; onStateChanged(STARTED); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mCalledOnSaveInstanceState = true; checkGetActivity(); // FIXME: We should not allow onSaveInstanceState except when STARTED or greater. // But FragmentManager currently does it in saveAllState for fragments on the // back stack, so fragments may be in the CREATED state. checkStateAtLeast("onSaveInstanceState", CREATED); } @Override public void onPause() { super.onPause(); mCalledOnPause = true; checkState("onPause", RESUMED); mState = STARTED; onStateChanged(RESUMED); } @Override public void onStop() { super.onStop(); mCalledOnStop = true; checkState("onStop", STARTED); mState = CREATED; onStateChanged(STARTED); } @Override public void onDestroy() { super.onDestroy(); mCalledOnDestroy = true; checkState("onDestroy", CREATED); mState = ATTACHED; onStateChanged(CREATED); } @Override public void onDetach() { super.onDetach(); mCalledOnDetach = true; checkState("onDestroy", CREATED, ATTACHED); int fromState = mState; mState = DETACHED; onStateChanged(fromState); } }