• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.testing;
18 
19 import android.app.Application;
20 import android.content.Context;
21 import com.google.auto.value.AutoValue;
22 import dagger.hilt.EntryPoints;
23 import dagger.hilt.android.internal.Contexts;
24 import dagger.hilt.android.internal.testing.TestApplicationComponentManagerHolder;
25 import dagger.hilt.internal.GeneratedComponentManager;
26 import dagger.hilt.internal.Preconditions;
27 import java.util.ArrayList;
28 import java.util.List;
29 
30 /**
31  * Provides access to the Singleton component in tests, so that Rules can access it after custom
32  * test modules have been added.
33  */
34 public final class OnComponentReadyRunner {
35   private final List<EntryPointListener<?>> listeners = new ArrayList<>();
36   private GeneratedComponentManager<?> componentManager;
37   private boolean componentHostSet = false;
38 
39   /** Used by generated code, to notify listeners that the component has been created. */
setComponentManager(GeneratedComponentManager<?> componentManager)40   public void setComponentManager(GeneratedComponentManager<?> componentManager) {
41     Preconditions.checkState(!componentHostSet, "Component host was already set.");
42     componentHostSet = true;
43     this.componentManager = componentManager;
44     for (EntryPointListener<?> listener : listeners) {
45       listener.deliverComponent(componentManager);
46     }
47   }
48 
49   /** Must be called on the test thread, before the Statement is evaluated. */
addListener( Context context, Class<T> entryPoint, OnComponentReadyListener<T> listener)50   public static <T> void addListener(
51       Context context, Class<T> entryPoint, OnComponentReadyListener<T> listener) {
52     Application application = Contexts.getApplication(context.getApplicationContext());
53     if (application instanceof TestApplicationComponentManagerHolder) {
54       TestApplicationComponentManagerHolder managerHolder =
55           (TestApplicationComponentManagerHolder) application;
56       OnComponentReadyRunnerHolder runnerHolder =
57           (OnComponentReadyRunnerHolder) managerHolder.componentManager();
58       runnerHolder.getOnComponentReadyRunner().addListenerInternal(entryPoint, listener);
59     }
60   }
61 
addListenerInternal(Class<T> entryPoint, OnComponentReadyListener<T> listener)62   private <T> void addListenerInternal(Class<T> entryPoint, OnComponentReadyListener<T> listener) {
63     if (componentHostSet) {
64       // If the componentHost was already set, just call through immediately
65       runListener(componentManager, entryPoint, listener);
66     } else {
67       listeners.add(EntryPointListener.create(entryPoint, listener));
68     }
69   }
70 
isEmpty()71   public boolean isEmpty() {
72     return listeners.isEmpty();
73   }
74 
75   @AutoValue
76   abstract static class EntryPointListener<T> {
create( Class<T> entryPoint, OnComponentReadyListener<T> listener)77     static <T> EntryPointListener<T> create(
78         Class<T> entryPoint, OnComponentReadyListener<T> listener) {
79       return new AutoValue_OnComponentReadyRunner_EntryPointListener<T>(entryPoint, listener);
80     }
81 
entryPoint()82     abstract Class<T> entryPoint();
83 
listener()84     abstract OnComponentReadyListener<T> listener();
85 
deliverComponent(GeneratedComponentManager<?> object)86     private void deliverComponent(GeneratedComponentManager<?> object) {
87       runListener(object, entryPoint(), listener());
88     }
89   }
90 
runListener( GeneratedComponentManager<?> componentManager, Class<T> entryPoint, OnComponentReadyListener<T> listener)91   private static <T> void runListener(
92       GeneratedComponentManager<?> componentManager,
93       Class<T> entryPoint,
94       OnComponentReadyListener<T> listener) {
95     try {
96       listener.onComponentReady(EntryPoints.get(componentManager, entryPoint));
97     } catch (RuntimeException | Error t) {
98       throw t;
99     } catch (Throwable t) {
100       throw new RuntimeException(t);
101     }
102   }
103 
104   /** Public for use by generated code and {@link TestApplicationComponentManager} */
105   public interface OnComponentReadyRunnerHolder {
getOnComponentReadyRunner()106     OnComponentReadyRunner getOnComponentReadyRunner();
107   }
108 
109   /** Rules should register an implementation of this to get access to the singleton component */
110   public interface OnComponentReadyListener<T> {
onComponentReady(T entryPoint)111     void onComponentReady(T entryPoint) throws Throwable;
112   }
113 }
114