• 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 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