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