1 /* 2 * Copyright (C) 2021 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.flags; 18 19 import android.content.Context; 20 import dagger.Module; 21 import dagger.hilt.EntryPoint; 22 import dagger.hilt.InstallIn; 23 import dagger.hilt.android.EntryPointAccessors; 24 import dagger.hilt.components.SingletonComponent; 25 import dagger.hilt.internal.Preconditions; 26 import dagger.multibindings.Multibinds; 27 import java.lang.annotation.ElementType; 28 import java.lang.annotation.Target; 29 import java.util.Set; 30 import javax.inject.Qualifier; 31 32 /** 33 * Runtime flag for the Fragment.getContext() fix. See https://github.com/google/dagger/pull/2620 34 * for this change. Controls if fragment code should use the fixed getContext() behavior where it 35 * correctly returns null after a fragment is removed. This fixed behavior matches the behavior of a 36 * regular, non-Hilt fragment and can help catch issues where a removed or leaked fragment is 37 * incorrectly used. 38 * 39 * <p>In order to set the flag, bind a boolean value qualified with 40 * {@link DisableFragmentGetContextFix} into a set in the {@code SingletonComponent}. A set is used 41 * instead of an optional binding to avoid a dependency on Guava. Only one value may be bound into 42 * the set within a given app. If this is not set, the default is to not use the fix. Example for 43 * binding the value: 44 * 45 * <pre><code> 46 * {@literal @}Module 47 * {@literal @}InstallIn(SingletonComponent.class) 48 * public final class DisableFragmentGetContextFixModule { 49 * {@literal @}Provides 50 * {@literal @}IntoSet 51 * {@literal @}FragmentGetContextFix.DisableFragmentGetContextFix 52 * static Boolean provideDisableFragmentGetContextFix() { 53 * return // true or false depending on some rollout logic for your app 54 * } 55 * } 56 * </code></pre> 57 */ 58 public final class FragmentGetContextFix { 59 60 /** Qualifier annotation to bind disable the Fragment.getContext() fix at runtime. */ 61 @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) 62 @Qualifier 63 public @interface DisableFragmentGetContextFix {} 64 isFragmentGetContextFixDisabled(Context context)65 public static boolean isFragmentGetContextFixDisabled(Context context) { 66 // Use a set here instead of an optional to avoid the Guava dependency 67 Set<Boolean> flagSet = EntryPointAccessors.fromApplication( 68 context, FragmentGetContextFixEntryPoint.class).getDisableFragmentGetContextFix(); 69 70 // TODO(b/199927963): Consider adding a plugin to check this at compile time 71 Preconditions.checkState(flagSet.size() <= 1, 72 "Cannot bind the flag @DisableFragmentGetContextFix more than once."); 73 74 if (flagSet.isEmpty()) { 75 return true; 76 } else { 77 return flagSet.iterator().next(); 78 } 79 } 80 81 /** Entry point for getting the flag. */ 82 @EntryPoint 83 @InstallIn(SingletonComponent.class) 84 public interface FragmentGetContextFixEntryPoint { getDisableFragmentGetContextFix()85 @DisableFragmentGetContextFix Set<Boolean> getDisableFragmentGetContextFix(); 86 } 87 88 /** Declare the empty flag set. */ 89 @Module 90 @InstallIn(SingletonComponent.class) 91 abstract static class FragmentGetContextFixModule { 92 @Multibinds 93 @DisableFragmentGetContextFix disableFragmentGetContextFix()94 abstract Set<Boolean> disableFragmentGetContextFix(); 95 } 96 FragmentGetContextFix()97 private FragmentGetContextFix() { 98 } 99 } 100