• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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