diff --git a/java/dagger/hilt/BUILD b/java/dagger/hilt/BUILD index 19b13f4fa2d..426d6bcb5ec 100644 --- a/java/dagger/hilt/BUILD +++ b/java/dagger/hilt/BUILD @@ -118,6 +118,7 @@ filegroup( "//java/dagger/hilt/android:srcs_filegroup", "//java/dagger/hilt/android/components:srcs_filegroup", "//java/dagger/hilt/android/qualifiers:srcs_filegroup", + "//java/dagger/hilt/android/internal:srcs_filegroup", "//java/dagger/hilt/android/internal/builders:srcs_filegroup", "//java/dagger/hilt/android/internal/lifecycle:srcs_filegroup", "//java/dagger/hilt/android/internal/managers:srcs_filegroup", diff --git a/java/dagger/hilt/android/ActivityRetainedLifecycle.java b/java/dagger/hilt/android/ActivityRetainedLifecycle.java new file mode 100644 index 00000000000..5e2d7cdcb66 --- /dev/null +++ b/java/dagger/hilt/android/ActivityRetainedLifecycle.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.hilt.android; + +import android.app.Activity; +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; + +/** + * A ActivityRetainedLifecycle class is associated with the lifecycle of the {@link + * dagger.hilt.android.components.ActivityRetainedComponent}. + */ +public interface ActivityRetainedLifecycle { + + /** + * Adds a new {@link OnClearedListener} for receiving a callback when the activity retained + * instances will no longer be needed and destroyed. + * + * @param listener The listener that should be added. + */ + @MainThread + void addOnClearedListener(@NonNull OnClearedListener listener); + + /** + * Removes a {@link OnClearedListener} previously added via {@link + * #addOnClearedListener(OnClearedListener)}. + * + * @param listener The listener that should be removed. + */ + @MainThread + void removeOnClearedListener(@NonNull OnClearedListener listener); + + /** + * Listener for receiving a callback for when the {@link + * dagger.hilt.android.components.ActivityRetainedComponent} will no longer be used and destroyed. + */ + interface OnClearedListener { + + /** + * Called when the activity retained instances will no longer be used and destroyed. + * + *

Specifically this will be invoked during {@link Activity#onDestroy()} when {@link + * Activity#isChangingConfigurations} is false. + */ + void onCleared(); + } +} diff --git a/java/dagger/hilt/android/BUILD b/java/dagger/hilt/android/BUILD index fde2633b589..1f14047a979 100644 --- a/java/dagger/hilt/android/BUILD +++ b/java/dagger/hilt/android/BUILD @@ -88,6 +88,15 @@ android_library( ], ) +android_library( + name = "activity_retained_lifecycle", + srcs = ["ActivityRetainedLifecycle.java"], + deps = [ + ":package_info", + "@maven//:androidx_annotation_annotation", + ], +) + android_library( name = "artifact-lib", tags = ["maven_coordinates=com.google.dagger:hilt-android:" + POM_VERSION_ALPHA], @@ -126,11 +135,13 @@ gen_maven_artifact( "//java/dagger/hilt:generates_root_input", "//java/dagger/hilt:install_in", "//java/dagger/hilt:package_info", + "//java/dagger/hilt/android:activity_retained_lifecycle", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:hilt_android_app", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/components", "//java/dagger/hilt/android/components:package_info", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", "//java/dagger/hilt/android/internal/lifecycle", "//java/dagger/hilt/android/internal/managers", diff --git a/java/dagger/hilt/android/internal/BUILD b/java/dagger/hilt/android/internal/BUILD new file mode 100644 index 00000000000..57c84e3ebce --- /dev/null +++ b/java/dagger/hilt/android/internal/BUILD @@ -0,0 +1,28 @@ +# Copyright (C) 2020 The Dagger Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Description: +# Internal Hilt Android utitlies + +package(default_visibility = ["//:src"]) + +android_library( + name = "internal", + srcs = ["ThreadUtil.java"], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/java/dagger/hilt/android/internal/ThreadUtil.java b/java/dagger/hilt/android/internal/ThreadUtil.java new file mode 100644 index 00000000000..d5b11f58c34 --- /dev/null +++ b/java/dagger/hilt/android/internal/ThreadUtil.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.hilt.android.internal; + +import android.os.Looper; + +/** Thread utility methods. */ +public final class ThreadUtil { + + private static Thread mainThread; + + private ThreadUtil() {} + + /** Returns true if the current thread is the Main thread. */ + public static boolean isMainThread() { + if (mainThread == null) { + mainThread = Looper.getMainLooper().getThread(); + } + return Thread.currentThread() == mainThread; + } + + /** Checks that the current thread is the Main thread. Otherwise throws an exception. */ + public static void ensureMainThread() { + if (!isMainThread()) { + throw new IllegalStateException("Must be called on the Main thread."); + } + } +} diff --git a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java index d5bff3bf83f..1a2c073c198 100644 --- a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java @@ -21,24 +21,40 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.activity.ComponentActivity; +import dagger.Binds; +import dagger.Module; import dagger.hilt.EntryPoint; import dagger.hilt.EntryPoints; import dagger.hilt.InstallIn; +import dagger.hilt.android.ActivityRetainedLifecycle; import dagger.hilt.android.components.ActivityRetainedComponent; import dagger.hilt.android.components.ApplicationComponent; +import dagger.hilt.android.internal.ThreadUtil; import dagger.hilt.android.internal.builders.ActivityRetainedComponentBuilder; +import dagger.hilt.android.scopes.ActivityRetainedScoped; import dagger.hilt.internal.GeneratedComponentManager; +import java.util.HashSet; +import java.util.Set; +import javax.inject.Inject; /** A manager for the creation of components that survives activity configuration changes. */ final class ActivityRetainedComponentManager implements GeneratedComponentManager { + /** Entry point for {@link ActivityRetainedComponentBuilder}. */ @EntryPoint @InstallIn(ApplicationComponent.class) - public interface LifecycleComponentBuilderEntryPoint { + public interface ActivityRetainedComponentBuilderEntryPoint { ActivityRetainedComponentBuilder retainedComponentBuilder(); } + /** Entry point for {@link Lifecycle}. */ + @EntryPoint + @InstallIn(ActivityRetainedComponent.class) + public interface ActivityRetainedLifecycleEntryPoint { + ActivityRetainedLifecycle getActivityRetainedLifecycle(); + } + static final class ActivityRetainedComponentViewModel extends ViewModel { private final ActivityRetainedComponent component; @@ -49,6 +65,15 @@ static final class ActivityRetainedComponentViewModel extends ViewModel { ActivityRetainedComponent getComponent() { return component; } + + @Override + protected void onCleared() { + super.onCleared(); + ActivityRetainedLifecycle lifecycle = + EntryPoints.get(component, ActivityRetainedLifecycleEntryPoint.class) + .getActivityRetainedLifecycle(); + ((ActivityRetainedComponentManager.Lifecycle) lifecycle).dispatchOnCleared(); + } } private final ViewModelProvider viewModelProvider; @@ -67,7 +92,8 @@ ActivityRetainedComponent getComponent() { public T create(@NonNull Class aClass) { ActivityRetainedComponent component = EntryPoints.get( - activity.getApplication(), LifecycleComponentBuilderEntryPoint.class) + activity.getApplication(), + ActivityRetainedComponentBuilderEntryPoint.class) .retainedComponentBuilder() .build(); return (T) new ActivityRetainedComponentViewModel(component); @@ -91,4 +117,38 @@ private ActivityRetainedComponent createComponent() { return viewModelProvider.get(ActivityRetainedComponentViewModel.class).getComponent(); } + /** The default implementation of {@link ActivityRetainedLifecycle}. */ + @ActivityRetainedScoped + static final class Lifecycle implements ActivityRetainedLifecycle { + + private final Set listeners = new HashSet<>(); + + @Inject + Lifecycle() {} + + @Override + public void addOnClearedListener(@NonNull OnClearedListener listener) { + ThreadUtil.ensureMainThread(); + listeners.add(listener); + } + + @Override + public void removeOnClearedListener(@NonNull OnClearedListener listener) { + ThreadUtil.ensureMainThread(); + listeners.remove(listener); + } + + void dispatchOnCleared() { + for (OnClearedListener listener : listeners) { + listener.onCleared(); + } + } + } + + @Module + @InstallIn(ActivityRetainedComponent.class) + abstract static class LifecycleModule { + @Binds + abstract ActivityRetainedLifecycle bind(Lifecycle impl); + } } diff --git a/java/dagger/hilt/android/internal/managers/BUILD b/java/dagger/hilt/android/internal/managers/BUILD index 899762eb3ce..52806cceac7 100644 --- a/java/dagger/hilt/android/internal/managers/BUILD +++ b/java/dagger/hilt/android/internal/managers/BUILD @@ -35,10 +35,14 @@ android_library( ], deps = [ ":component_supplier", + "//:dagger_with_compiler", "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:activity_retained_lifecycle", "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", + "//java/dagger/hilt/android/scopes:activity_retained_scoped", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:preconditions", "@maven//:androidx_activity_activity",