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