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