1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.fragment.app 18 19 import android.content.Context 20 import android.os.Bundle 21 import android.util.AttributeSet 22 import androidx.annotation.LayoutRes 23 24 /** 25 * This fragment watches its primary lifecycle events and throws IllegalStateException if any of 26 * them are called out of order or from a bad/unexpected state. 27 */ 28 open class StrictFragment(@LayoutRes contentLayoutId: Int = 0) : Fragment(contentLayoutId) { 29 var currentState: State = State.DETACHED 30 31 var calledOnAttach: Boolean = false 32 var calledOnCreate: Boolean = false 33 var calledOnActivityCreated: Boolean = false 34 var calledOnStart: Boolean = false 35 var calledOnResume: Boolean = false 36 var calledOnSaveInstanceState: Boolean = false 37 var calledOnPause: Boolean = false 38 var calledOnStop: Boolean = false 39 var calledOnDestroy: Boolean = false 40 var calledOnDetach: Boolean = false 41 var calledOnAttachFragment: Boolean = false 42 var lastSavedInstanceState: Bundle? = null 43 onStateChangednull44 open fun onStateChanged(fromState: State) { 45 checkGetActivity() 46 } 47 checkGetActivitynull48 fun checkGetActivity() { 49 checkNotNull(activity) { "getActivity() returned null at unexpected time" } 50 } 51 checkActivityNotDestroyednull52 fun checkActivityNotDestroyed() { 53 check(!requireActivity().isDestroyed) 54 } 55 checkStatenull56 fun checkState(caller: String, vararg expected: State) { 57 if (expected.isEmpty()) { 58 throw IllegalArgumentException("must supply at least one expected state") 59 } 60 for (expect in expected) { 61 if (currentState == expect) { 62 return 63 } 64 } 65 val expectString = StringBuilder(expected[0].toString()) 66 for (i in 1 until expected.size) { 67 expectString.append(" or ").append(expected[i]) 68 } 69 throw IllegalStateException( 70 "$caller called while fragment was $currentState; " + "expected $expectString" 71 ) 72 } 73 checkStateAtLeastnull74 fun checkStateAtLeast(caller: String, minState: State) { 75 if (currentState < minState) { 76 throw IllegalStateException( 77 "$caller called while fragment was $currentState; " + "expected at least $minState" 78 ) 79 } 80 } 81 82 @Deprecated("Deprecated in superclass") onAttachFragmentnull83 override fun onAttachFragment(childFragment: Fragment) { 84 calledOnAttachFragment = true 85 } 86 onInflatenull87 override fun onInflate(context: Context, attrs: AttributeSet, savedInstanceState: Bundle?) { 88 super.onInflate(context, attrs, savedInstanceState) 89 checkActivityNotDestroyed() 90 checkState("onInflate", State.DETACHED) 91 } 92 onAttachnull93 override fun onAttach(context: Context) { 94 super.onAttach(context) 95 checkActivityNotDestroyed() 96 calledOnAttach = true 97 checkState("onAttach", State.DETACHED) 98 currentState = State.ATTACHED 99 onStateChanged(State.DETACHED) 100 @Suppress("Deprecation") // We're not setting retainInstance, just supporting it 101 if (retainInstance && calledOnCreate) { 102 // We were created previously 103 currentState = State.CREATED 104 onStateChanged(State.ATTACHED) 105 } 106 } 107 onCreatenull108 override fun onCreate(savedInstanceState: Bundle?) { 109 super.onCreate(savedInstanceState) 110 checkActivityNotDestroyed() 111 if (calledOnCreate && !calledOnDestroy) { 112 throw IllegalStateException("onCreate called more than once with no onDestroy") 113 } 114 calledOnCreate = true 115 lastSavedInstanceState = savedInstanceState 116 checkState("onCreate", State.ATTACHED) 117 currentState = State.CREATED 118 onStateChanged(State.ATTACHED) 119 } 120 121 @Deprecated("Deprecated in superclass") onActivityCreatednull122 override fun onActivityCreated(savedInstanceState: Bundle?) { 123 @Suppress("Deprecation") // we're just calling the superclass method with the same name 124 super.onActivityCreated(savedInstanceState) 125 checkActivityNotDestroyed() 126 calledOnActivityCreated = true 127 checkState("onActivityCreated", State.ATTACHED, State.CREATED, State.VIEW_CREATED) 128 val fromState = currentState 129 currentState = State.ACTIVITY_CREATED 130 onStateChanged(fromState) 131 } 132 onStartnull133 override fun onStart() { 134 super.onStart() 135 checkActivityNotDestroyed() 136 calledOnStart = true 137 checkState("onStart", State.CREATED, State.ACTIVITY_CREATED) 138 currentState = State.STARTED 139 onStateChanged(State.ACTIVITY_CREATED) 140 } 141 onResumenull142 override fun onResume() { 143 super.onResume() 144 checkActivityNotDestroyed() 145 calledOnResume = true 146 checkState("onResume", State.STARTED) 147 currentState = State.RESUMED 148 onStateChanged(State.STARTED) 149 } 150 onSaveInstanceStatenull151 override fun onSaveInstanceState(outState: Bundle) { 152 super.onSaveInstanceState(outState) 153 calledOnSaveInstanceState = true 154 checkGetActivity() 155 // FIXME: We should not allow onSaveInstanceState except when STARTED or greater. 156 // But FragmentManager currently does it in saveAllState for fragments on the 157 // back stack, so fragments may be in the CREATED state. 158 checkStateAtLeast("onSaveInstanceState", State.CREATED) 159 } 160 onPausenull161 override fun onPause() { 162 super.onPause() 163 calledOnPause = true 164 checkState("onPause", State.RESUMED) 165 currentState = State.STARTED 166 onStateChanged(State.RESUMED) 167 } 168 onStopnull169 override fun onStop() { 170 super.onStop() 171 calledOnStop = true 172 checkState("onStop", State.STARTED) 173 currentState = State.CREATED 174 onStateChanged(State.STARTED) 175 } 176 onDestroynull177 override fun onDestroy() { 178 super.onDestroy() 179 calledOnDestroy = true 180 checkState("onDestroy", State.CREATED) 181 currentState = State.ATTACHED 182 onStateChanged(State.CREATED) 183 } 184 onDetachnull185 override fun onDetach() { 186 super.onDetach() 187 calledOnDetach = true 188 checkState("onDestroy", State.CREATED, State.ATTACHED) 189 val fromState = currentState 190 currentState = State.DETACHED 191 onStateChanged(fromState) 192 } 193 194 enum class State { 195 DETACHED, 196 ATTACHED, 197 CREATED, 198 VIEW_CREATED, 199 ACTIVITY_CREATED, 200 STARTED, 201 RESUMED 202 } 203 } 204