原文 : How AndroidX changes the way we work with Activities and Fragments

Author: Miłosz Lewandowski

Translator: Fly_with24

The Activity/Fragmet API on AndroidX has changed a lot over the years. Let’s take a look at how they make Android development more efficient and how they fit into popular programming rules and patterns.

All of the features described in this article are now available in stable AndroidX packages that were released or moved to stable versions last year.

Pass the layout ID in the constructor

Fragment from AndroidX AppCompat 1.1.0 and Fragment 1.1.0 To get started, you can use a constructor that takes layoutId as an argument:

class MyActivity : AppCompatActivity(R.layout.my_activity)
class MyFragmentActivity: FragmentActivity(R.layout.my_fragment_activity)
class MyFragment : Fragment(R.layout.my_fragment)
Copy the code

This approach reduces the number of method overrides in activities/Fragments and makes classes more readable. The setContentView() method is called without overwriting onCreate() in the Activity. In addition, you can manually call Inflater to expand the view without manually overwriting onCreateView in the Fragment.

Extend the flexibility of activities/fragments

With the new API on AndroidX, you can reduce the number of activities/fragments that need to be handled. In general, you can get an object that provides some functionality and register your processing logic with it, rather than overwriting methods in your Activity/Fragment. As a result, you can now compose several separate classes on the screen, gaining greater flexibility, reusing code, and generally having more control over code structure without introducing your own abstractions. Let’s see how this works in two examples.

1. OnBackPressedDispatcher

Sometimes, you need to prevent users from going back to the previous level. In this case, you need to override the onBackPressed() method in the Activity. However, when you use fragments, there is no direct way to intercept the return. There is no onBackPressed() method available in the Fragment class to prevent unexpected behavior when multiple fragments are present at the same time.

However, starting with AndroidX Activity 1.0.0, you can use OnBackPressedDispatcher anywhere you can access the Activity’s code (for example, Register OnBackPressedCallback in Fragment.

class MyFragment : Fragment() {
  override fun onAttach(context: Context) {
    super.onAttach(context)
    val callback = object : OnBackPressedCallback(true) {
      override fun handleOnBackPressed(a) {
        // Do something
      }
    }
    requireActivity().onBackPressedDispatcher.addCallback(this, callback)
  }
}
Copy the code

You might notice two other useful features here:

  • OnBackPressedCallbackBoolean type arguments in the constructor of help dynamically turn on/off press behavior based on current state
  • addCallback()The optional first argument to the method isLifecycleOwnerTo ensure that objects are aware only during your life cycle (for example,Fragment) at least inSTARTEDThe callback is used only when state.

By using OnBackPressedDispatcher, you not only get a convenient way to handle the return key outside of your Activity. Depending on your needs, you can define OnBackPressedCallback anywhere, make it reusable, or do anything based on the architecture of your application. You no longer need to override the onBackPressed method in your Activity, nor do you need to provide your own abstract code to implement the requirements.

2. SavedStateRegistry

You might want to use the Saved State function if you want your Activity to resume its previous state after it is stopped and restarted. In the past, you needed to override two methods in your Activity: onSaveInstanceState and onRestoreInstanceState. You can also access the restored state in the onCreate method. Also, in fragments, you can use the onSaveInstanceState method (and restore state in the onCreate, onCreateView, and onActivityCreated methods).

From AndroidX SavedState 1.0.0 (which is a dependency inside AndroidX Activity and AndroidX Fragment). To get started, you can visit SavedStateRegistry, which uses a mechanism similar to the OnBackPressedDispatcher described earlier: You can get SavedStateRegistry from your Activity/Fragment and register your SavedStateProvider:

class MyActivity : AppCompatActivity() {

  companion object {
    private const val MY_SAVED_STATE_KEY = "my_saved_state"
    private const val SOME_VALUE_KEY = "some_value"
  }
    
  private lateinit var someValue: String
    
  private val savedStateProvider = SavedStateRegistry.SavedStateProvider {    
    Bundle().apply {
      putString(SOME_VALUE_KEY, someValue)
    }
  }
  
  override fun onCreate(savedInstanceState: Bundle?). {    
    super.onCreate(savedInstanceState)
    savedStateRegistry
      .registerSavedStateProvider(MY_SAVED_STATE_KEY, savedStateProvider)
  }
  
  fun someMethod(a){ someValue = savedStateRegistry .consumeRestoredStateForKey(MY_SAVED_STATE_KEY) ? .getString(SOME_VALUE_KEY) ? :""}}Copy the code

As you can see, SavedStateRegistry forces you to use the key for data. This prevents your data from being corrupted by another SavedStateProvider that attaches to the same Activity/Fragment. Just as in OnBackPressedDispatcher, you can, for example, extract SavedStateProvider into another class and use it with data using whatever logic you need to achieve a clean save state behavior in your application.

Also, if you use viewModels in your applications, consider using AndroidX ViewModel-savedState so that your ViewModel can save its state. AndroidX Fragment 1.2.0 and AndroidX Fragment 1.1.0 for convenience, Enable SavedState SavedStateViewModelFactory is used in the get all the way of the ViewModel default factory: Delegate the ViewModelProvider constructor and viewModelproviders.of () method.

FragmentFactory

One of the most commonly cited problems with Fragments is the inability to use constructors with arguments. For example, if you use Dagger2 for dependency injection, you cannot annotate the Fragment constructor with the Inject parameter. You can now reduce similar problems during Fragment creation by specifying the FragmentFactory class. You can override the default method for instantiating fragments by registering the FragmentFactory in the FragmentManager:

class MyFragmentFactory : FragmentFactory() {

  override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
    // Call loadFragmentClass() to obtain the Class object
    val fragmentClass = loadFragmentClass(classLoader, className)
     
    // Now you can use className/fragmentClass to determine your prefered way 
    // of instantiating the Fragment object and just do it here.
        
    // Or just call regular FragmentFactory to instantiate the Fragment using
    // no arguments constructor
    return super.instantiate(classLoader, className)
  }
}
Copy the code

As you can see, the API is very generic, so you can do anything you want to create a Fragment instance. Returning to the Dagger2 example, for example, you can inject the FragmentFactory Provider

and use it to get the Fragment object.

Test fragments

Starting with AndroidX Fragment 1.1.0, you can use the Fragment testing component to provide the FragmentScenario class, which helps instantiate the Fragment in a test and test it separately:

// To launch a Fragment with a user interface:
val scenario = launchFragmentInContainer<FirstFragment>()
        
// To launch a headless Fragment:
val scenario = launchFragment<FirstFragment>()
        
// To move the fragment to specific lifecycle state:
scenario.moveToState(CREATED)

// Now you can e.g. perform actions using Espresso:
onView(withId(R.id.refresh)).perform(click())

// To obtain a Fragment instance:
scenario.onFragment { fragment ->
  ...
}
Copy the code

More Kotlin!

It’s nice to see that the -kTX AndroidX package offers many useful Kotlin extension methods, and new ones are added regularly. For example, in AndroidX Fragment-KTX 1.2.0, extensions that use fragmentated types can be used with the replace() method on FragmentTransaction. Using this in conjunction with the commit() extension method, we get the following code:

// Before
supportFragmentManager
  .beginTransaction()
  .add(R.id.container, MyFragment::class.java, null)
  .commit()

// After
supportFragmentManager.commit {
  replace<MyFragment>(R.id.container)
}
Copy the code

FragmentContainerView

A small but important thing. If you use FrameLayout as a Fragment container, use FragmentContainerView instead. It fixes some animation z-index order issues and window insert scheduling. FragmentContainerView is available from AndroidX Fragment 1.2.0.

About me

I am a Fly_with24

  • The Denver nuggets

  • Jane’s book

  • Github