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