diff options
author | Ian Lake <ilake@google.com> | 2019-03-19 10:14:07 -0700 |
---|---|---|
committer | Ian Lake <ilake@google.com> | 2019-03-25 14:48:35 -0700 |
commit | 2f634eec2143acd6a4ea19a375a6e3877cdcc2ed (patch) | |
tree | 657eaa105401a9b7ccd6b3b84e89844443423974 | |
parent | 8044e8eb9ac9af014d38e655cd3170828f5bd635 (diff) | |
download | support-2f634eec2143acd6a4ea19a375a6e3877cdcc2ed.tar.gz |
Convert @ContentView to constructor annotation
As layout IDs are non-final in library modules
(and in the future, app modules), change
@ContentView to only be a marker annotation for
a constructor which developers can call with
a specific layout ID.
Fixes: 128352521
Fixes: 127531658
Test: updated tests
Change-Id: I15e2edee8cbd68180991f89fbc3b04e12c961ede
25 files changed, 160 insertions, 114 deletions
diff --git a/activity/api/1.0.0-alpha06.txt b/activity/api/1.0.0-alpha06.txt index c46e0cdedea..16e509e06b1 100644 --- a/activity/api/1.0.0-alpha06.txt +++ b/activity/api/1.0.0-alpha06.txt @@ -3,6 +3,7 @@ package androidx.activity { public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner { ctor public ComponentActivity(); + ctor @ContentView public ComponentActivity(@LayoutRes int); method @Deprecated public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback); method @Deprecated public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback); method @Deprecated public Object? getLastCustomNonConfigurationInstance(); diff --git a/activity/api/current.txt b/activity/api/current.txt index c46e0cdedea..16e509e06b1 100644 --- a/activity/api/current.txt +++ b/activity/api/current.txt @@ -3,6 +3,7 @@ package androidx.activity { public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner { ctor public ComponentActivity(); + ctor @ContentView public ComponentActivity(@LayoutRes int); method @Deprecated public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback); method @Deprecated public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback); method @Deprecated public Object? getLastCustomNonConfigurationInstance(); diff --git a/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt b/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt index be432311bdd..eff215cfa2b 100644 --- a/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt +++ b/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt @@ -18,7 +18,6 @@ package androidx.activity import android.widget.TextView import androidx.activity.test.R -import androidx.annotation.ContentView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.rule.ActivityTestRule @@ -43,5 +42,4 @@ class ContentViewTest { } } -@ContentView(R.layout.activity_inflates_res) -class ContentViewActivity : ComponentActivity() +class ContentViewActivity : ComponentActivity(R.layout.activity_inflates_res) diff --git a/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/src/main/java/androidx/activity/ComponentActivity.java index 1619920c1d3..2a3e3e9d453 100644 --- a/activity/src/main/java/androidx/activity/ComponentActivity.java +++ b/activity/src/main/java/androidx/activity/ComponentActivity.java @@ -25,6 +25,7 @@ import android.view.Window; import androidx.annotation.CallSuper; import androidx.annotation.ContentView; +import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.arch.core.util.Cancellable; @@ -39,7 +40,6 @@ import androidx.savedstate.SavedStateRegistry; import androidx.savedstate.SavedStateRegistryController; import androidx.savedstate.SavedStateRegistryOwner; -import java.util.HashMap; import java.util.WeakHashMap; /** @@ -73,9 +73,14 @@ public class ComponentActivity extends androidx.core.app.ComponentActivity imple private final WeakHashMap<OnBackPressedCallback, Cancellable> mOnBackPressedCallbackCancellables = new WeakHashMap<>(); - // Cache the ContentView layoutIds for Activities. - private static final HashMap<Class, Integer> sAnnotationIds = new HashMap<>(); + @LayoutRes + private int mContentLayoutId; + /** + * Default constructor for ComponentActivity. All Activities must have a default constructor + * for API 27 and lower devices or when using the default + * {@link android.app.AppComponentFactory}. + */ public ComponentActivity() { Lifecycle lifecycle = getLifecycle(); //noinspection ConstantConditions @@ -116,6 +121,22 @@ public class ComponentActivity extends androidx.core.app.ComponentActivity imple } /** + * Alternate constructor that can be used to provide a default layout + * that will be inflated as part of <code>super.onCreate(savedInstanceState)</code>. + * + * <p>This should generally be called from your constructor that takes no parameters, + * as is required for API 27 and lower or when using the default + * {@link android.app.AppComponentFactory}. + * + * @see #ComponentActivity() + */ + @ContentView + public ComponentActivity(@LayoutRes int contentLayoutId) { + this(); + mContentLayoutId = contentLayoutId; + } + + /** * {@inheritDoc} * * If your ComponentActivity is annotated with {@link ContentView}, this will @@ -126,18 +147,8 @@ public class ComponentActivity extends androidx.core.app.ComponentActivity imple super.onCreate(savedInstanceState); mSavedStateRegistryController.performRestore(savedInstanceState); ReportFragment.injectIfNeededIn(this); - Class<? extends ComponentActivity> clazz = getClass(); - if (!sAnnotationIds.containsKey(clazz)) { - ContentView annotation = clazz.getAnnotation(ContentView.class); - if (annotation != null) { - sAnnotationIds.put(clazz, annotation.value()); - } else { - sAnnotationIds.put(clazz, null); - } - } - Integer layoutId = sAnnotationIds.get(clazz); - if (layoutId != null && layoutId != 0) { - setContentView(layoutId); + if (mContentLayoutId != 0) { + setContentView(mContentLayoutId); } } diff --git a/annotations/api/1.1.0-beta01.txt b/annotations/api/1.1.0-beta01.txt index 491ffea2f98..7e260ad643c 100644 --- a/annotations/api/1.1.0-beta01.txt +++ b/annotations/api/1.1.0-beta01.txt @@ -41,8 +41,7 @@ package androidx.annotation { @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface ColorRes { } - @java.lang.annotation.Inherited @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public @interface ContentView { - method @LayoutRes public abstract int value(); + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface ContentView { } @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DimenRes { diff --git a/annotations/api/current.txt b/annotations/api/current.txt index 491ffea2f98..7e260ad643c 100644 --- a/annotations/api/current.txt +++ b/annotations/api/current.txt @@ -41,8 +41,7 @@ package androidx.annotation { @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface ColorRes { } - @java.lang.annotation.Inherited @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public @interface ContentView { - method @LayoutRes public abstract int value(); + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface ContentView { } @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DimenRes { diff --git a/annotations/src/main/java/androidx/annotation/ContentView.java b/annotations/src/main/java/androidx/annotation/ContentView.java index eee0d2f63bf..f2f098dea4a 100644 --- a/annotations/src/main/java/androidx/annotation/ContentView.java +++ b/annotations/src/main/java/androidx/annotation/ContentView.java @@ -16,31 +16,31 @@ package androidx.annotation; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** - * Annotation that can be attached to a component such as an - * androidx.activity.ComponentActivity or {@link androidx.fragment.app.Fragment} + * Annotation that can be attached to a constructor with a single {@link LayoutRes} parameter * to denote what layout the component intends to inflate and set as its content. * <p> - * This annotation is marked as {@link Inherited} and will therefore apply to subclasses - * automatically. - * <p> * It is strongly recommended that components that support this annotation specifically call * it out in their documentation. + * <pre> + * public class MainFragment extends Fragment { + * public MainFragment() { + * // This constructor is annotated with @ContentView + * super(R.layout.main); + * } + * } + * </pre> * - * @see androidx.activity.ComponentActivity#onCreate(android.os.Bundle) - * @see androidx.fragment.app.Fragment#onCreateView + * @see androidx.activity.ComponentActivity#ComponentActivity(int) + * @see androidx.fragment.app.Fragment#Fragment(int) */ -@Retention(RUNTIME) -@Target({TYPE}) -@Inherited +@Retention(CLASS) +@Target({CONSTRUCTOR}) public @interface ContentView { - @LayoutRes - int value(); } diff --git a/appcompat/api/1.1.0-alpha04.txt b/appcompat/api/1.1.0-alpha04.txt index fdf6fc553a0..03862a1c660 100644 --- a/appcompat/api/1.1.0-alpha04.txt +++ b/appcompat/api/1.1.0-alpha04.txt @@ -218,6 +218,7 @@ package androidx.appcompat.app { public class AppCompatActivity extends androidx.fragment.app.FragmentActivity implements androidx.appcompat.app.ActionBarDrawerToggle.DelegateProvider androidx.appcompat.app.AppCompatCallback androidx.core.app.TaskStackBuilder.SupportParentable { ctor public AppCompatActivity(); + ctor @ContentView public AppCompatActivity(@LayoutRes int); method public androidx.appcompat.app.AppCompatDelegate getDelegate(); method public androidx.appcompat.app.ActionBarDrawerToggle.Delegate? getDrawerToggleDelegate(); method public androidx.appcompat.app.ActionBar? getSupportActionBar(); diff --git a/appcompat/api/current.txt b/appcompat/api/current.txt index fdf6fc553a0..03862a1c660 100644 --- a/appcompat/api/current.txt +++ b/appcompat/api/current.txt @@ -218,6 +218,7 @@ package androidx.appcompat.app { public class AppCompatActivity extends androidx.fragment.app.FragmentActivity implements androidx.appcompat.app.ActionBarDrawerToggle.DelegateProvider androidx.appcompat.app.AppCompatCallback androidx.core.app.TaskStackBuilder.SupportParentable { ctor public AppCompatActivity(); + ctor @ContentView public AppCompatActivity(@LayoutRes int); method public androidx.appcompat.app.AppCompatDelegate getDelegate(); method public androidx.appcompat.app.ActionBarDrawerToggle.Delegate? getDrawerToggleDelegate(); method public androidx.appcompat.app.ActionBar? getSupportActionBar(); diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java index 23c9a09012a..8e7c7fd9c25 100644 --- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java +++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java @@ -31,6 +31,7 @@ import android.view.ViewGroup; import android.view.Window; import androidx.annotation.CallSuper; +import androidx.annotation.ContentView; import androidx.annotation.IdRes; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; @@ -67,6 +68,30 @@ public class AppCompatActivity extends FragmentActivity implements AppCompatCall private AppCompatDelegate mDelegate; private Resources mResources; + /** + * Default constructor for AppCompatActivity. All Activities must have a default constructor + * for API 27 and lower devices or when using the default + * {@link android.app.AppComponentFactory}. + */ + public AppCompatActivity() { + super(); + } + + /** + * Alternate constructor that can be used to provide a default layout + * that will be inflated as part of <code>super.onCreate(savedInstanceState)</code>. + * + * <p>This should generally be called from your constructor that takes no parameters, + * as is required for API 27 and lower or when using the default + * {@link android.app.AppComponentFactory}. + * + * @see #AppCompatActivity() + */ + @ContentView + public AppCompatActivity(@LayoutRes int contentLayoutId) { + super(contentLayoutId); + } + @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); diff --git a/fragment/api/1.1.0-alpha06.txt b/fragment/api/1.1.0-alpha06.txt index 22f79078af1..c945b0b4ff0 100644 --- a/fragment/api/1.1.0-alpha06.txt +++ b/fragment/api/1.1.0-alpha06.txt @@ -27,6 +27,7 @@ package androidx.fragment.app { public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner { ctor public Fragment(); + ctor @ContentView public Fragment(@LayoutRes int); method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String[]?); method public final boolean equals(Object?); method public final androidx.fragment.app.FragmentActivity? getActivity(); @@ -157,6 +158,7 @@ package androidx.fragment.app { public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator { ctor public FragmentActivity(); + ctor @ContentView public FragmentActivity(@LayoutRes int); method public androidx.fragment.app.FragmentManager getSupportFragmentManager(); method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager(); method public android.content.Context getThemedContext(); diff --git a/fragment/api/current.txt b/fragment/api/current.txt index 22f79078af1..c945b0b4ff0 100644 --- a/fragment/api/current.txt +++ b/fragment/api/current.txt @@ -27,6 +27,7 @@ package androidx.fragment.app { public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner { ctor public Fragment(); + ctor @ContentView public Fragment(@LayoutRes int); method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String[]?); method public final boolean equals(Object?); method public final androidx.fragment.app.FragmentActivity? getActivity(); @@ -157,6 +158,7 @@ package androidx.fragment.app { public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator { ctor public FragmentActivity(); + ctor @ContentView public FragmentActivity(@LayoutRes int); method public androidx.fragment.app.FragmentManager getSupportFragmentManager(); method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager(); method public android.content.Context getThemedContext(); diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt index 1e12a684487..a90cccf97b4 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt @@ -17,7 +17,6 @@ package androidx.fragment.app import android.os.Bundle -import androidx.annotation.ContentView import androidx.fragment.app.test.EmptyFragmentTestActivity import androidx.fragment.test.R import androidx.test.annotation.UiThreadTest @@ -87,8 +86,7 @@ class FragmentFactoryTest { } } -@ContentView(R.layout.nested_inflated_fragment_parent) -class ParentFragment : Fragment() { +class ParentFragment : Fragment(R.layout.nested_inflated_fragment_parent) { var factory: FragmentFactory? = null override fun onCreate(savedInstanceState: Bundle?) { diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt index d27247cf918..42d18fc4de2 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt @@ -25,7 +25,6 @@ import android.view.LayoutInflater import android.view.Menu import android.view.ViewGroup import android.widget.TextView -import androidx.annotation.ContentView import androidx.core.view.ViewCompat import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks import androidx.fragment.app.FragmentTestUtil.HostCallbacks @@ -868,11 +867,10 @@ class FragmentLifecycleTest { } } - @ContentView(R.layout.nested_retained_inflated_fragment_parent) - class RetainedInflatedParentFragment : Fragment() + class RetainedInflatedParentFragment : + Fragment(R.layout.nested_retained_inflated_fragment_parent) - @ContentView(R.layout.nested_inflated_fragment_child) - class RetainedInflatedChildFragment : Fragment() { + class RetainedInflatedChildFragment : Fragment(R.layout.nested_inflated_fragment_child) { internal var mOnInflateCount = 0 override fun onCreate(savedInstanceState: Bundle?) { diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt index 8bf7bbdc3c2..85adb6c7ad6 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver -import androidx.annotation.ContentView import androidx.annotation.RequiresApi import androidx.fragment.app.test.FragmentTestActivity import androidx.fragment.test.R @@ -309,12 +308,9 @@ class FragmentTest { } } - @ContentView(R.layout.fragment_a) - class FragmentA : Fragment() + class FragmentA : Fragment(R.layout.fragment_a) - @ContentView(R.layout.fragment_b) - class FragmentB : Fragment() + class FragmentB : Fragment(R.layout.fragment_b) - @ContentView(R.layout.fragment_c) - class FragmentC : Fragment() + class FragmentC : Fragment(R.layout.fragment_c) } diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt index 0c1e3fbac37..b66c15fed23 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt @@ -15,14 +15,10 @@ */ package androidx.fragment.app.test -import org.junit.Assert.fail - import android.content.Intent import android.os.Bundle import android.os.SystemClock import android.view.LayoutInflater - -import androidx.annotation.ContentView import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTestUtil @@ -34,8 +30,8 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage - import org.junit.After +import org.junit.Assert.fail import org.junit.Before import org.junit.Rule import org.junit.Test @@ -407,8 +403,7 @@ class FragmentTransactionTest { private inner class NonStaticFragment : Fragment() - @ContentView(R.layout.fragment_a) - class OnGetLayoutInflaterFragment : Fragment() { + class OnGetLayoutInflaterFragment : Fragment(R.layout.fragment_a) { var onGetLayoutInflaterCalls = 0 lateinit var baseLayoutInflater: LayoutInflater diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt index c1d4d40154a..4b2ff74461d 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt @@ -15,14 +15,10 @@ */ package androidx.fragment.app -import org.junit.Assert.fail - import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup - -import androidx.annotation.ContentView import androidx.fragment.app.test.FragmentTestActivity import androidx.fragment.test.R import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -31,7 +27,7 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage - +import org.junit.Assert.fail import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -1029,8 +1025,7 @@ class FragmentViewTest { } } - @ContentView(R.layout.fragment_a) - class SimpleViewFragment : Fragment() { + class SimpleViewFragment : Fragment(R.layout.fragment_a) { var onCreateViewCount: Int = 0 override fun onCreateView( diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.kt index af65c8c2448..59c3d43507f 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView -import androidx.annotation.ContentView import androidx.fragment.app.test.FragmentTestActivity import androidx.fragment.test.R import androidx.test.annotation.UiThreadTest @@ -83,8 +82,7 @@ class NestedInflatedFragmentTest { fm.executePendingTransactions() } - @ContentView(R.layout.nested_inflated_fragment_parent) - open class ParentFragment : Fragment() + open class ParentFragment : Fragment(R.layout.nested_inflated_fragment_parent) class UserVisibleHintParentFragment : ParentFragment() { override fun setUserVisibleHint(isVisibleToUser: Boolean) { @@ -102,8 +100,7 @@ class NestedInflatedFragmentTest { } } - @ContentView(R.layout.nested_inflated_fragment_child) - class InflatedChildFragment : Fragment() + class InflatedChildFragment : Fragment(R.layout.nested_inflated_fragment_child) class SimpleFragment : Fragment() { override fun onCreateView( diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt index b8faca2d4f1..61d74b5811d 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt @@ -18,13 +18,14 @@ package androidx.fragment.app import android.content.Context import android.os.Bundle +import androidx.annotation.LayoutRes import com.google.common.truth.Truth.assertWithMessage /** * This fragment watches its primary lifecycle events and throws IllegalStateException * if any of them are called out of order or from a bad/unexpected state. */ -open class StrictFragment : Fragment() { +open class StrictFragment(@LayoutRes contentLayoutId: Int = 0) : Fragment(contentLayoutId) { var currentState: Int = 0 var calledOnAttach: Boolean = false diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt index 5b367e46301..86062b96aee 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt @@ -25,8 +25,8 @@ import androidx.fragment.test.R import com.google.common.truth.Truth.assertWithMessage open class StrictViewFragment( - @LayoutRes val contentLayoutId: Int = R.layout.strict_view_fragment -) : StrictFragment() { + @LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment +) : StrictFragment(contentLayoutId) { internal var onCreateViewCalled: Boolean = false internal var onViewCreatedCalled: Boolean = false @@ -39,12 +39,9 @@ open class StrictViewFragment( ): View? { checkGetActivity() checkState("onCreateView", StrictFragment.CREATED) - var result = super.onCreateView(inflater, container, savedInstanceState) - if (result == null) { - result = inflater.inflate(contentLayoutId, container, false) + return super.onCreateView(inflater, container, savedInstanceState).also { + onCreateViewCalled = true } - onCreateViewCalled = true - return result } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt index 6ab2b107657..64dbc8d186a 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt @@ -18,7 +18,6 @@ package androidx.fragment.app.test import android.content.Context import android.content.Intent import android.os.Bundle -import androidx.annotation.ContentView import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.test.R @@ -26,8 +25,7 @@ import androidx.fragment.test.R /** * A simple activity used for Fragment Transitions and lifecycle event ordering */ -@ContentView(R.layout.activity_content) -class FragmentTestActivity : FragmentActivity() { +class FragmentTestActivity : FragmentActivity(R.layout.activity_content) { class ParentFragment : Fragment() { var wasAttachedInTime: Boolean = false diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt index ea44bf90e2b..9baf280fc5d 100644 --- a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt +++ b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt @@ -20,8 +20,6 @@ import android.content.Context import android.os.Bundle import android.view.View import android.widget.TextView - -import androidx.annotation.ContentView import androidx.fragment.app.Fragment import androidx.fragment.test.R import androidx.loader.app.LoaderManager @@ -29,8 +27,8 @@ import androidx.loader.content.AsyncTaskLoader import androidx.loader.content.Loader import androidx.testutils.RecreatedActivity -@ContentView(R.layout.activity_loader) -class LoaderActivity : RecreatedActivity(), LoaderManager.LoaderCallbacks<String> { +class LoaderActivity : RecreatedActivity(R.layout.activity_loader), + LoaderManager.LoaderCallbacks<String> { lateinit var textView: TextView lateinit var textViewB: TextView @@ -75,8 +73,8 @@ class LoaderActivity : RecreatedActivity(), LoaderManager.LoaderCallbacks<String } } - @ContentView(R.layout.fragment_c) - class TextLoaderFragment : Fragment(), LoaderManager.LoaderCallbacks<String> { + class TextLoaderFragment : Fragment(R.layout.fragment_c), + LoaderManager.LoaderCallbacks<String> { lateinit var textView: TextView override fun onCreate(savedInstanceState: Bundle?) { diff --git a/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/src/main/java/androidx/fragment/app/Fragment.java index de3963dcf3f..89cf832443f 100644 --- a/fragment/src/main/java/androidx/fragment/app/Fragment.java +++ b/fragment/src/main/java/androidx/fragment/app/Fragment.java @@ -48,6 +48,7 @@ import android.widget.AdapterView; import androidx.annotation.CallSuper; import androidx.annotation.ContentView; +import androidx.annotation.LayoutRes; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -72,7 +73,6 @@ import androidx.savedstate.SavedStateRegistryOwner; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; import java.util.UUID; /** @@ -250,8 +250,8 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener SavedStateRegistryController mSavedStateRegistryController; - // Cache the ContentView layoutIds for Fragments. - private static final HashMap<Class, Integer> sAnnotationIds = new HashMap<>(); + @LayoutRes + private int mContentLayoutId; /** * {@inheritDoc} @@ -414,13 +414,14 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener } /** - * Default constructor. <strong>Every</strong> fragment must have an - * empty constructor, so it can be instantiated when restoring its - * activity's state. It is strongly recommended that subclasses do not - * have other constructors with parameters, since these constructors - * will not be called when the fragment is re-instantiated; instead, - * arguments can be supplied by the caller with {@link #setArguments} - * and later retrieved by the Fragment with {@link #getArguments}. + * Constructor used by the default {@link FragmentFactory}. You must + * {@link FragmentManager#setFragmentFactory(FragmentFactory) set a custom FragmentFactory} + * if you want to use a non-default constructor to ensure that your constructor + * is called when the fragment is re-instantiated. + * + * <p>It is strongly recommended to supply arguments with {@link #setArguments} + * and later retrieved by the Fragment with {@link #getArguments}. These arguments + * are automatically saved and restored alongside the Fragment. * * <p>Applications should generally not implement a constructor. Prefer * {@link #onAttach(Context)} instead. It is the first place application code can run where @@ -432,6 +433,19 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener initLifecycle(); } + /** + * Alternate constructor that can be used to provide a default layout + * that will be inflated by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. + * + * @see #Fragment() + * @see #onCreateView(LayoutInflater, ViewGroup, Bundle) + */ + @ContentView + public Fragment(@LayoutRes int contentLayoutId) { + this(); + mContentLayoutId = contentLayoutId; + } + private void initLifecycle() { mLifecycleRegistry = new LifecycleRegistry(this); mSavedStateRegistryController = SavedStateRegistryController.create(this); @@ -1616,9 +1630,8 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener * Called to have the fragment instantiate its user interface view. * This is optional, and non-graphical fragments can return null. This will be called between * {@link #onCreate(Bundle)} and {@link #onActivityCreated(Bundle)}. - * <p>The default implementation looks for an {@link ContentView} annotation, inflating - * and returning that layout. If the annotation is not found or has an invalid layout resource - * id, this method returns null. + * <p>A default View can be returned by calling {@link #Fragment(int)} in your + * constructor. Otherwise, this method returns null. * * <p>It is recommended to <strong>only</strong> inflate the layout in this method and move * logic that operates on the returned View to {@link #onViewCreated(View, Bundle)}. @@ -1639,18 +1652,8 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener @Nullable public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - Class<? extends Fragment> clazz = getClass(); - if (!sAnnotationIds.containsKey(clazz)) { - ContentView annotation = clazz.getAnnotation(ContentView.class); - if (annotation != null) { - sAnnotationIds.put(clazz, annotation.value()); - } else { - sAnnotationIds.put(clazz, null); - } - } - Integer layoutId = sAnnotationIds.get(clazz); - if (layoutId != null && layoutId != 0) { - return inflater.inflate(layoutId, container, false); + if (mContentLayoutId != 0) { + return inflater.inflate(mContentLayoutId, container, false); } return null; } diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java index 4a4a1f10e84..6cee60b608b 100644 --- a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java +++ b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java @@ -37,6 +37,8 @@ import android.view.Window; import androidx.activity.ComponentActivity; import androidx.activity.OnBackPressedCallback; import androidx.annotation.CallSuper; +import androidx.annotation.ContentView; +import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; @@ -107,8 +109,33 @@ public class FragmentActivity extends ComponentActivity implements // for startActivityForResult calls where a result has not yet been delivered. SparseArrayCompat<String> mPendingFragmentActivityResults; + /** + * Default constructor for FragmentActivity. All Activities must have a default constructor + * for API 27 and lower devices or when using the default + * {@link android.app.AppComponentFactory}. + */ public FragmentActivity() { super(); + init(); + } + + /** + * Alternate constructor that can be used to provide a default layout + * that will be inflated as part of <code>super.onCreate(savedInstanceState)</code>. + * + * <p>This should generally be called from your constructor that takes no parameters, + * as is required for API 27 and lower or when using the default + * {@link android.app.AppComponentFactory}. + * + * @see #FragmentActivity() + */ + @ContentView + public FragmentActivity(@LayoutRes int contentLayoutId) { + super(contentLayoutId); + init(); + } + + private void init() { // Route onBackPressed() callbacks to the FragmentManager getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback() { @Override diff --git a/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt b/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt index 22b74aa9e9a..1463395f37f 100644 --- a/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt +++ b/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt @@ -17,6 +17,7 @@ package androidx.testutils import android.os.Bundle +import androidx.annotation.LayoutRes import androidx.fragment.app.FragmentActivity import java.util.concurrent.CountDownLatch @@ -25,7 +26,9 @@ import java.util.concurrent.CountDownLatch * In order to use this class, have your activity extend it and call * [FragmentActivityUtils.recreateActivity] API. */ -open class RecreatedActivity : FragmentActivity() { +open class RecreatedActivity( + @LayoutRes contentLayoutId: Int = 0 +) : FragmentActivity(contentLayoutId) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) |