• 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 @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 == Contexts.getApplication(context.getApplicationContext())) {
147       // If we searched for a type but ended up on the application, 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