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