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