1 /* 2 * Copyright (C) 2019 The Dagger Authors. 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 dagger.hilt.android.internal.managers; 18 19 import android.content.Context; 20 import android.content.ContextWrapper; 21 import androidx.fragment.app.Fragment; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import androidx.lifecycle.Lifecycle; 25 import androidx.lifecycle.LifecycleEventObserver; 26 import androidx.lifecycle.LifecycleOwner; 27 import dagger.hilt.EntryPoint; 28 import dagger.hilt.EntryPoints; 29 import dagger.hilt.InstallIn; 30 import dagger.hilt.android.components.ActivityComponent; 31 import dagger.hilt.android.components.FragmentComponent; 32 import dagger.hilt.android.internal.Contexts; 33 import dagger.hilt.android.internal.builders.ViewComponentBuilder; 34 import dagger.hilt.android.internal.builders.ViewWithFragmentComponentBuilder; 35 import dagger.hilt.internal.GeneratedComponentManager; 36 import dagger.hilt.internal.Preconditions; 37 38 /** 39 * Do not use except in Hilt generated code! 40 * 41 * <p>A manager for the creation of components that live in the View. 42 * 43 * <p>Note: This class is not typed since its type in generated code is always <?> or <Object>. This 44 * is mainly due to the fact that we don't know the components at the time of generation, and 45 * because even the injector interface type is not a valid type if we have a hilt base class. 46 */ 47 public final class ViewComponentManager implements GeneratedComponentManager<Object> { 48 /** Entrypoint for {@link ViewWithFragmentComponentBuilder}. */ 49 @EntryPoint 50 @InstallIn(FragmentComponent.class) 51 public interface ViewWithFragmentComponentBuilderEntryPoint { viewWithFragmentComponentBuilder()52 ViewWithFragmentComponentBuilder viewWithFragmentComponentBuilder(); 53 } 54 55 /** Entrypoint for {@link ViewComponentBuilder}. */ 56 @EntryPoint 57 @InstallIn(ActivityComponent.class) 58 public interface ViewComponentBuilderEntryPoint { viewComponentBuilder()59 ViewComponentBuilder viewComponentBuilder(); 60 } 61 62 private volatile Object component; 63 private final Object componentLock = new Object(); 64 private final boolean hasFragmentBindings; 65 private final View view; 66 ViewComponentManager(View view, boolean hasFragmentBindings)67 public ViewComponentManager(View view, boolean hasFragmentBindings) { 68 this.view = view; 69 this.hasFragmentBindings = hasFragmentBindings; 70 } 71 72 @Override generatedComponent()73 public Object generatedComponent() { 74 if (component == null) { 75 synchronized (componentLock) { 76 if (component == null) { 77 component = createComponent(); 78 } 79 } 80 } 81 return component; 82 } 83 createComponent()84 private Object createComponent() { 85 GeneratedComponentManager<?> componentManager = 86 getParentComponentManager(/*allowMissing=*/ false); 87 if (hasFragmentBindings) { 88 return EntryPoints.get(componentManager, ViewWithFragmentComponentBuilderEntryPoint.class) 89 .viewWithFragmentComponentBuilder() 90 .view(view) 91 .build(); 92 } else { 93 return EntryPoints.get(componentManager, ViewComponentBuilderEntryPoint.class) 94 .viewComponentBuilder() 95 .view(view) 96 .build(); 97 } 98 } 99 100 /* Returns the component manager of the parent or null if not found. */ maybeGetParentComponentManager()101 public GeneratedComponentManager<?> maybeGetParentComponentManager() { 102 return getParentComponentManager(/*allowMissing=*/ true); 103 } 104 getParentComponentManager(boolean allowMissing)105 private GeneratedComponentManager<?> getParentComponentManager(boolean allowMissing) { 106 if (hasFragmentBindings) { 107 Context context = getParentContext(FragmentContextWrapper.class, allowMissing); 108 if (context instanceof FragmentContextWrapper) { 109 110 FragmentContextWrapper fragmentContextWrapper = (FragmentContextWrapper) context; 111 return (GeneratedComponentManager<?>) fragmentContextWrapper.getFragment(); 112 } else if (allowMissing) { 113 // We didn't find anything, so return null if we're not supposed to fail. 114 // The rest of the logic is just about getting a good error message. 115 return null; 116 } 117 118 // Check if there was a valid parent component, just not a Fragment, to give a more 119 // specific error. 120 Context parent = getParentContext(GeneratedComponentManager.class, allowMissing); 121 Preconditions.checkState( 122 !(parent instanceof GeneratedComponentManager), 123 "%s, @WithFragmentBindings Hilt view must be attached to an " 124 + "@AndroidEntryPoint Fragment. " 125 + "Was attached to context %s", 126 view.getClass(), 127 parent.getClass().getName()); 128 } else { 129 Context context = getParentContext(GeneratedComponentManager.class, allowMissing); 130 if (context instanceof GeneratedComponentManager) { 131 return (GeneratedComponentManager<?>) context; 132 } else if (allowMissing) { 133 return null; 134 } 135 } 136 137 // Couldn't find any parent components to descend from. 138 throw new IllegalStateException( 139 String.format( 140 "%s, Hilt view must be attached to an @AndroidEntryPoint Fragment or Activity.", 141 view.getClass())); 142 143 } 144 getParentContext(Class<?> parentType, boolean allowMissing)145 private Context getParentContext(Class<?> parentType, boolean allowMissing) { 146 Context context = unwrap(view.getContext(), parentType); 147 if (context == Contexts.getApplication(context.getApplicationContext())) { 148 // If we searched for a type but ended up on the application, just return null 149 // as this is never what we are looking for 150 Preconditions.checkState( 151 allowMissing, 152 "%s, Hilt view cannot be created using the application context. " 153 + "Use a Hilt Fragment or Activity context.", 154 view.getClass()); 155 return null; 156 } 157 return context; 158 } 159 unwrap(Context context, Class<?> target)160 private static Context unwrap(Context context, Class<?> target) { 161 while (context instanceof ContextWrapper && !target.isInstance(context)) { 162 context = ((ContextWrapper) context).getBaseContext(); 163 } 164 return context; 165 } 166 167 /** 168 * Do not use except in Hilt generated code! 169 * 170 * <p>A wrapper class to expose the {@link Fragment} to the views they're inflating. 171 */ 172 public static final class FragmentContextWrapper extends ContextWrapper { 173 private Fragment fragment; 174 private LayoutInflater baseInflater; 175 private LayoutInflater inflater; 176 private final LifecycleEventObserver fragmentLifecycleObserver = 177 new LifecycleEventObserver() { 178 @Override 179 public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { 180 if (event == Lifecycle.Event.ON_DESTROY) { 181 // Prevent the fragment from leaking if the view outlives the fragment. 182 // See https://github.com/google/dagger/issues/2070 183 FragmentContextWrapper.this.fragment = null; 184 FragmentContextWrapper.this.baseInflater = null; 185 FragmentContextWrapper.this.inflater = null; 186 } 187 } 188 }; 189 FragmentContextWrapper(Context base, Fragment fragment)190 FragmentContextWrapper(Context base, Fragment fragment) { 191 super(Preconditions.checkNotNull(base)); 192 this.baseInflater = null; 193 this.fragment = Preconditions.checkNotNull(fragment); 194 this.fragment.getLifecycle().addObserver(fragmentLifecycleObserver); 195 } 196 FragmentContextWrapper(LayoutInflater baseInflater, Fragment fragment)197 FragmentContextWrapper(LayoutInflater baseInflater, Fragment fragment) { 198 super(Preconditions.checkNotNull(Preconditions.checkNotNull(baseInflater).getContext())); 199 this.baseInflater = baseInflater; 200 this.fragment = Preconditions.checkNotNull(fragment); 201 this.fragment.getLifecycle().addObserver(fragmentLifecycleObserver); 202 } 203 getFragment()204 Fragment getFragment() { 205 Preconditions.checkNotNull(fragment, "The fragment has already been destroyed."); 206 return fragment; 207 } 208 209 @Override getSystemService(String name)210 public Object getSystemService(String name) { 211 if (!LAYOUT_INFLATER_SERVICE.equals(name)) { 212 return getBaseContext().getSystemService(name); 213 } 214 if (inflater == null) { 215 if (baseInflater == null) { 216 baseInflater = 217 (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE); 218 } 219 inflater = baseInflater.cloneInContext(this); 220 } 221 return inflater; 222 } 223 } 224 } 225