/*
* Copyright (C) 2019 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.hilt.android.internal.managers;
import android.content.Context;
import android.content.ContextWrapper;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import dagger.hilt.EntryPoint;
import dagger.hilt.EntryPoints;
import dagger.hilt.InstallIn;
import dagger.hilt.android.components.ActivityComponent;
import dagger.hilt.android.components.FragmentComponent;
import dagger.hilt.android.internal.builders.ViewComponentBuilder;
import dagger.hilt.android.internal.builders.ViewWithFragmentComponentBuilder;
import dagger.hilt.internal.GeneratedComponentManager;
import dagger.hilt.internal.Preconditions;
/**
* Do not use except in Hilt generated code!
*
*
A manager for the creation of components that live in the View.
*
*
Note: This class is not typed since its type in generated code is always > or . This
* is mainly due to the fact that we don't know the components at the time of generation, and
* because even the injector interface type is not a valid type if we have a hilt base class.
*/
public final class ViewComponentManager implements GeneratedComponentManager {
/** Entrypoint for {@link ViewWithFragmentComponentBuilder}. */
@EntryPoint
@InstallIn(FragmentComponent.class)
public interface ViewWithFragmentComponentBuilderEntryPoint {
ViewWithFragmentComponentBuilder viewWithFragmentComponentBuilder();
}
/** Entrypoint for {@link ViewComponentBuilder}. */
@EntryPoint
@InstallIn(ActivityComponent.class)
public interface ViewComponentBuilderEntryPoint {
ViewComponentBuilder viewComponentBuilder();
}
private volatile Object component;
private final Object componentLock = new Object();
private final boolean hasFragmentBindings;
private final View view;
public ViewComponentManager(View view, boolean hasFragmentBindings) {
this.view = view;
this.hasFragmentBindings = hasFragmentBindings;
}
@Override
public Object generatedComponent() {
if (component == null) {
synchronized (componentLock) {
if (component == null) {
component = createComponent();
}
}
}
return component;
}
private Object createComponent() {
GeneratedComponentManager> componentManager =
getParentComponentManager(/*allowMissing=*/ false);
if (hasFragmentBindings) {
return EntryPoints.get(componentManager, ViewWithFragmentComponentBuilderEntryPoint.class)
.viewWithFragmentComponentBuilder()
.view(view)
.build();
} else {
return EntryPoints.get(componentManager, ViewComponentBuilderEntryPoint.class)
.viewComponentBuilder()
.view(view)
.build();
}
}
/* Returns the component manager of the parent or null if not found. */
public GeneratedComponentManager> maybeGetParentComponentManager() {
return getParentComponentManager(/*allowMissing=*/ true);
}
private GeneratedComponentManager> getParentComponentManager(boolean allowMissing) {
if (hasFragmentBindings) {
Context context = getParentContext(FragmentContextWrapper.class, allowMissing);
if (context instanceof FragmentContextWrapper) {
FragmentContextWrapper fragmentContextWrapper = (FragmentContextWrapper) context;
return (GeneratedComponentManager>) fragmentContextWrapper.fragment;
} else if (allowMissing) {
// We didn't find anything, so return null if we're not supposed to fail.
// The rest of the logic is just about getting a good error message.
return null;
}
// Check if there was a valid parent component, just not a Fragment, to give a more
// specific error.
Context parent = getParentContext(GeneratedComponentManager.class, allowMissing);
Preconditions.checkState(
!(parent instanceof GeneratedComponentManager),
"%s, @WithFragmentBindings Hilt view must be attached to an "
+ "@AndroidEntryPoint Fragment. "
+ "Was attached to context %s",
view.getClass(),
parent.getClass().getName());
} else {
Context context = getParentContext(GeneratedComponentManager.class, allowMissing);
if (context instanceof GeneratedComponentManager) {
return (GeneratedComponentManager>) context;
} else if (allowMissing) {
return null;
}
}
// Couldn't find any parent components to descend from.
throw new IllegalStateException(
String.format(
"%s, Hilt view must be attached to an @AndroidEntryPoint Fragment or Activity.",
view.getClass()));
}
private Context getParentContext(Class> parentType, boolean allowMissing) {
Context context = unwrap(view.getContext(), parentType);
if (context == unwrap(context.getApplicationContext(), GeneratedComponentManager.class)) {
// If we searched for a type but ended up on the application context, just return null
// as this is never what we are looking for
Preconditions.checkState(
allowMissing,
"%s, Hilt view cannot be created using the application context. "
+ "Use a Hilt Fragment or Activity context.",
view.getClass());
return null;
}
return context;
}
private static Context unwrap(Context context, Class> target) {
while (context instanceof ContextWrapper && !target.isInstance(context)) {
context = ((ContextWrapper) context).getBaseContext();
}
return context;
}
/**
* Do not use except in Hilt generated code!
*
* A wrapper class to expose the {@link Fragment} to the views they're inflating.
*/
// This is only non-final for the account override
public static final class FragmentContextWrapper extends ContextWrapper {
private LayoutInflater baseInflater;
private LayoutInflater inflater;
public final Fragment fragment;
public FragmentContextWrapper(Context base, Fragment fragment) {
super(Preconditions.checkNotNull(base));
this.baseInflater = null;
this.fragment = Preconditions.checkNotNull(fragment);
}
public FragmentContextWrapper(LayoutInflater baseInflater, Fragment fragment) {
super(Preconditions.checkNotNull(Preconditions.checkNotNull(baseInflater).getContext()));
this.baseInflater = baseInflater;
this.fragment = Preconditions.checkNotNull(fragment);
}
@Override
public Object getSystemService(String name) {
if (!LAYOUT_INFLATER_SERVICE.equals(name)) {
return getBaseContext().getSystemService(name);
}
if (inflater == null) {
if (baseInflater == null) {
baseInflater =
(LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
}
inflater = baseInflater.cloneInContext(this);
}
return inflater;
}
}
}