How AndroidX changes the way we work with Activities and Fragments
The article is based on the talks about what’s new in AndroidX I gave at the GDG Devfest 2019 events in Wrocław and Warsaw.
Over the last couple of months, many improvements to the Activity/Fragment APIs have been introduced via the AndroidX packages. Let’s see how they contribute to the state of Android development and how they fit into popular programming rules and patterns.
All of the features described in the article are now available in stable AndroidX packages and were released or moved to stable over the last year.
Layout resource ID in the constructor
Starting with AndroidX AppCompat 1.1.0 and Fragment 1.1.0, you can use the constructor that takes layoutId
as a parameter:
This approach can reduce the number of method overrides in your Activity/Fragment and make your classes more readable. There’s no need to override onCreate()
in your Activity only to call setContentView()
method. Also, there’s no need to override onCreateView()
in your Fragment just to manually call the Inflater
to inflate the view.
The flexibility of extending the Activity/Fragment behavior
With new APIs available in AndroidX, the shift is to reduce the Activity’s/Fragment’s direct responsibility of handling some of the functionalities. Instead, they can be provided via external classes. In general, the pattern is that instead of overriding methods in Activity/Fragment, you can obtain the object that provides some functionality and register your handler to it. In that way, you can now compose your screen of several independent classes, achieve higher flexibility, reuse the code, and, in general, have more control over the code structure without introducing your own abstractions. Let’s see how this works on two examples.
OnBackPressedDispatcher
Sometimes you need to prevent a user from going back in the navigation stack. In such a case, you needed to override the onBackPressed()
method in your Activity. However, there’s no direct way to intercept the behavior when you’re using Fragments. There’s just no onBackPressed()
method in the Fragment class available, and that’s to prevent the unexpected behavior when more than one Fragment is available at the same time.
However, starting with AndroidX Activity 1.0.0, you can use OnBackPressedDispatcher
to register your OnBackPressedCallback
in any place of your code where you have access to the Activity, for example in the Fragment:
You may notice two additional useful features here:
- the boolean parameter in the constructor of
OnBackPressedCallback
helps to turn on/off the back press behavior depending on the current state, - the optional first parameter of the
addCallback()
method is theLifecycleOwner
to ensure that the callback will be used only when your lifecycle-aware object (e.g. Fragment) is at least in theSTARTED
state.
By using OnBackPressedDispatcher
, you not only get the convenient way of handling the back presses outside the Activity. Depending on your needs, you can define the OnBackPressedCallback
wherever you want, make it reusable or do whatever you want to fit your app’s architecture. You’re no longer bound to override the onBackPressed
method in the Activity or to provide your own abstraction to achieve the code structure you need.
SavedStateRegistry
If you want your Activity to restore its state after the process is killed and started again, you may want to use the “saved state” functionality. Previously, you needed to override two methods in the Activity: onSaveInstanceState
and onRestoreInstanceState
. You can also access the restored state in the onCreate
method. Similarly, in Fragment, you have onSaveInstanceState
method available (and the restored state is available in the onCreate
, onCreateView
, and onActivityCreated
methods).
Starting with AndroidX SavedState 1.0.0, which is the dependency of the AndroidX Activity and the AndroidX Fragment, you get access to the SavedStateRegistry
which utilizes a similar mechanism as the OnBackPressedDispatcher
described previously: you can obtain the SavedStateRegistry
from the Activity/Fragment and then register your SavedStateProvider
:
As you can see, SavedStateRegistry
enforces you to use the key for your data. This can prevent your data from being corrupted by another SavedStateProvider
attached to the same Activity/Fragment. And as in the OnBackPressedDispatcher
, you can, for example, extract your SavedStateProvider
to another class, make it work with your data by using whatever abstraction you want and in such a way achieve the clean saved state behavior in your application.
Also, if you’re using ViewModel in your application, consider using AndroidX ViewModel-SavedState to enable your ViewModels to save their state. And for your convenience, starting with AndroidX Activity 1.1.0 and AndroidX Fragment 1.2.0, the SavedState-enabled SavedStateViewModelFactory
is the default factory used across all ways of obtaining ViewModel: delegates, ViewModelProvider
constructor and the ViewModelProviders.of()
method.
FragmentFactory
One of the most mentioned issues with Fragments is no possibility to use a constructor with arguments. For example, if you’re using Dagger2 for dependency injection, you cannot annotate the Fragment constructor with Inject
and specify the arguments. Now, you can mitigate this and similar issues with Fragment creation by specifying the FragmentFactory
class. By registering FragmentFactory
in your FragmentManager
, you can override the default way of instantiating the Fragment:
As you can see, the API is very general, so it gives you the ability to do whatever you want to create the Fragment instance. Going back to the Dagger2 example, you can, for example, inject to your FragmentFactory Provider<Fragment>
and use it to obtain the Fragment object.
Testing Fragments
Starting with AndroidX Fragment 1.1.0, the fragment-testing
artifact is available, providing the FragmentScenario
class that can help in instantiating the Fragment in your tests and testing it in isolation:
More Kotlin!
It’s nice to see that many useful Kotlin extension methods are available in the -ktx
AndroidX packages and the new ones are added regularly. For example, in AndroidX Fragment-KTX 1.2.0, the extension that uses reified type is available for the replace()
method on FragmentTransaction
. Combining it with the commit()
extension method, we can get the following code:
FragmentContainerView
Just a small but important thing to mention. If you’re using FrameLayout
as a parent to your Fragment, you should switch to FragmentContainerView
instead. It fixes some animation z-index order issues and window insets dispatching. The FragmentContainerView
is available starting with AndroidX Fragment 1.2.0.
What’s coming next?
Well, one can speculate, but nobody knows until it’s released! However, as you can see, it’s worth to check from time to time what’s new in AndroidX packages and how the changes can improve your day-to-day life as an Android developer.