• 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.internal.managers;
18 
19 import androidx.lifecycle.ViewModel;
20 import androidx.lifecycle.ViewModelProvider;
21 import androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 import androidx.activity.ComponentActivity;
24 import dagger.Binds;
25 import dagger.Module;
26 import dagger.hilt.EntryPoint;
27 import dagger.hilt.EntryPoints;
28 import dagger.hilt.InstallIn;
29 import dagger.hilt.android.ActivityRetainedLifecycle;
30 import dagger.hilt.android.components.ActivityRetainedComponent;
31 import dagger.hilt.android.internal.ThreadUtil;
32 import dagger.hilt.android.internal.builders.ActivityRetainedComponentBuilder;
33 import dagger.hilt.android.scopes.ActivityRetainedScoped;
34 import dagger.hilt.components.SingletonComponent;
35 import dagger.hilt.internal.GeneratedComponentManager;
36 import java.util.HashSet;
37 import java.util.Set;
38 import javax.inject.Inject;
39 
40 /** A manager for the creation of components that survives activity configuration changes. */
41 final class ActivityRetainedComponentManager
42     implements GeneratedComponentManager<ActivityRetainedComponent> {
43 
44   /** Entry point for {@link ActivityRetainedComponentBuilder}. */
45   @EntryPoint
46   @InstallIn(SingletonComponent.class)
47   public interface ActivityRetainedComponentBuilderEntryPoint {
retainedComponentBuilder()48     ActivityRetainedComponentBuilder retainedComponentBuilder();
49   }
50 
51   /** Entry point for {@link Lifecycle}. */
52   @EntryPoint
53   @InstallIn(ActivityRetainedComponent.class)
54   public interface ActivityRetainedLifecycleEntryPoint {
getActivityRetainedLifecycle()55     ActivityRetainedLifecycle getActivityRetainedLifecycle();
56   }
57 
58   static final class ActivityRetainedComponentViewModel extends ViewModel {
59     private final ActivityRetainedComponent component;
60 
ActivityRetainedComponentViewModel(ActivityRetainedComponent component)61     ActivityRetainedComponentViewModel(ActivityRetainedComponent component) {
62       this.component = component;
63     }
64 
getComponent()65     ActivityRetainedComponent getComponent() {
66       return component;
67     }
68 
69     @Override
onCleared()70     protected void onCleared() {
71       super.onCleared();
72       ActivityRetainedLifecycle lifecycle =
73           EntryPoints.get(component, ActivityRetainedLifecycleEntryPoint.class)
74               .getActivityRetainedLifecycle();
75       ((ActivityRetainedComponentManager.Lifecycle) lifecycle).dispatchOnCleared();
76     }
77   }
78 
79   private final ViewModelProvider viewModelProvider;
80 
81   @Nullable private volatile ActivityRetainedComponent component;
82   private final Object componentLock = new Object();
83 
ActivityRetainedComponentManager(ComponentActivity activity)84   ActivityRetainedComponentManager(ComponentActivity activity) {
85     this.viewModelProvider =
86         new ViewModelProvider(
87             activity,
88             new ViewModelProvider.Factory() {
89               @NonNull
90               @Override
91               @SuppressWarnings("unchecked")
92               public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
93                 ActivityRetainedComponent component =
94                     EntryPoints.get(
95                             activity.getApplication(),
96                             ActivityRetainedComponentBuilderEntryPoint.class)
97                         .retainedComponentBuilder()
98                         .build();
99                 return (T) new ActivityRetainedComponentViewModel(component);
100               }
101             });
102   }
103 
104   @Override
generatedComponent()105   public ActivityRetainedComponent generatedComponent() {
106     if (component == null) {
107       synchronized (componentLock) {
108         if (component == null) {
109           component = createComponent();
110         }
111       }
112     }
113     return component;
114   }
115 
createComponent()116   private ActivityRetainedComponent createComponent() {
117     return viewModelProvider.get(ActivityRetainedComponentViewModel.class).getComponent();
118   }
119 
120   /** The default implementation of {@link ActivityRetainedLifecycle}. */
121   @ActivityRetainedScoped
122   static final class Lifecycle implements ActivityRetainedLifecycle {
123 
124     private final Set<OnClearedListener> listeners = new HashSet<>();
125     private boolean onClearedDispatched = false;
126 
127     @Inject
Lifecycle()128     Lifecycle() {}
129 
130     @Override
addOnClearedListener(@onNull OnClearedListener listener)131     public void addOnClearedListener(@NonNull OnClearedListener listener) {
132       ThreadUtil.ensureMainThread();
133       throwIfOnClearedDispatched();
134       listeners.add(listener);
135     }
136 
137     @Override
removeOnClearedListener(@onNull OnClearedListener listener)138     public void removeOnClearedListener(@NonNull OnClearedListener listener) {
139       ThreadUtil.ensureMainThread();
140       throwIfOnClearedDispatched();
141       listeners.remove(listener);
142     }
143 
dispatchOnCleared()144     void dispatchOnCleared() {
145       ThreadUtil.ensureMainThread();
146       onClearedDispatched = true;
147       for (OnClearedListener listener : listeners) {
148         listener.onCleared();
149       }
150     }
151 
throwIfOnClearedDispatched()152     private void throwIfOnClearedDispatched() {
153       if (onClearedDispatched) {
154         throw new IllegalStateException(
155             "There was a race between the call to add/remove an OnClearedListener and onCleared(). "
156                 + "This can happen when posting to the Main thread from a background thread, "
157                 + "which is not supported.");
158       }
159     }
160   }
161 
162   @Module
163   @InstallIn(ActivityRetainedComponent.class)
164   abstract static class LifecycleModule {
165     @Binds
bind(Lifecycle impl)166     abstract ActivityRetainedLifecycle bind(Lifecycle impl);
167   }
168 }
169