Changes in using Activity and Fragment under AndroidX

Over the past period, the API of Activity / Fragmet under the AndroidX package has changed a lot. Let's see how they improve Android's development efficiency and how to adapt to the current popular programming rules and patterns.

All the features described in this article are now available in the stable AndroidX package, which was released or moved to a stable version last year.

Pass in the layout ID in the constructor

Starting with AndroidX AppCompat 1.1.0 and Fragment 1.1.0 (Translator's Note: AppCompat contains Fragment, and Fragment contains Activity, for details, see [Organization] Jetpack's main component dependencies and delivery relationships), you can use the layoutId as a parameter function:

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

This method can reduce the number of method rewrites in Activity / Fragment and make the class more readable. You can call setContentView () method without rewriting onCreate () in Activity. In addition, you can manually call Inflater to extend the view without manually rewriting onCreateView in Fragment.

Expand the flexibility of Activity / Fragment

With the help of the new API of AndroidX, it is possible to reduce the handling of certain functions in Activity / Fragment. In general, you can get objects that provide certain functions and register your processing logic with them instead of overriding the methods in Activity / Fragment. In this way, you can now compose several independent classes on the screen to gain greater flexibility, reuse code, and often have more control over the code structure without introducing your own abstractions. Let's see how this works in the two examples.

  1. OnBackPressedDispatcher

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

However, starting with AndroidX Activity 1.0.0, you can use OnBackPressedDispatcher to register OnBackPressedCallback anywhere you can access the code of the activity (for example, in Fragment).

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

You may notice two other useful functions here:

The boolean parameter in the constructor of OnBackPressedCallback helps to dynamically turn on / off the pressing behavior based on the current state

The optional first parameter of the addCallback () method is LifecycleOwner to ensure that the callback is only used when your lifecycle-aware object (for example, Fragment) is at least STARTED.

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

  1. SavedStateRegistry

If you want the Activity to return to its previous state after termination and restart, you may want to use the saved state feature. In the past, 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 can use the onSaveInstanceState method (and you can restore the state in the onCreate, onCreateView and onActivityCreated methods).

Starting with AndroidX SavedState 1.0.0 (which is a dependency within AndroidX Activity and AndroidX Fragment. Translator's Note: You don't need to declare it separately), you can access SavedStateRegistry, which uses a mechanism similar to OnBackPressedDispatcher described earlier: Get SavedStateRegistry from Activity / Fragment, then 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() {
    someValue = savedStateRegistry
      .consumeRestoredStateForKey(MY_SAVED_STATE_KEY)
      ?.getString(SOME_VALUE_KEY)
      ?: ""
  }
}

As you can see, SavedStateRegistry forces you to use the key for data. This prevents your data from being destroyed by another SavedStateProvider attached to the same Activity / Fragment. Just like in OnBackPressedDispatcher, you can, for example, extract the SavedStateProvider to another class and use it with any data by using any logic needed to achieve a clear save state behavior in the application.

In addition, if you use ViewModel in your application, consider using AndroidX ViewModel-SavedState so that your ViewModel can save its state. For convenience, as of AndroidX Activity 1.1.0 and AndroidXFragment 1.2.0, SavedState-enabled SavedStateViewModelFactory is the default factory used in all ways of obtaining ViewModel: delegate ViewModelProvider constructor and ViewModelProviders.of () method.

FragmentFactory

One of the most frequently mentioned problems with Fragment is that constructors with parameters cannot be used. For example, if you use Dagger2 for dependency injection, you cannot annotate the Fragment constructor with Inject and specify parameters. Now, you can reduce similar problems during the creation of Fragment by specifying the FragmentFactory class. By registering FragmentFactory in FragmentManager, you can override the default method of instantiating Fragment:

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)
  }
}

As you can see, the API is very general, so you can perform all the operations you want to create a Fragment instance. Back to the Dagger2 example, for example, you can inject FragmentFactory Provider <Fragment> and use it to get Fragment objects.

Test Fragment

Starting with AndroidX Fragment 1.1.0, you can use the Fragment test component to provide the FragmentScenario class, which can help instantiate the Fragment in the test and conduct a separate test:

// 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 ->

More Kotlin!

It's nice to see that -ktx AndroidX package provides many useful Kotlin extension methods, and new methods are added regularly. For example, in AndroidX Fragment-KTX 1.2.0, extensions using fragmented types can be used in the replace () method on FragmentTransaction. Using it in conjunction with the commit () extension method, we can obtain the following code:

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

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

FragmentContainerView

A small and important thing. If you use FrameLayout as a container for Fragment, you should use FragmentContainerView instead. It fixes some animation z-axis index order problems and window insertion scheduling. Starting from AndroidXFragment 1.2.0, FragmentContainerView can be used.

The article is not easy. If you like this article, or if it is helpful to you, I hope everyone can like it, forward it, and follow. The article will be updated continuously. Absolutely dry goods! ! !

Guess you like

Origin blog.51cto.com/14775360/2486716