1. What does the FragmentFactory mean?
Conventions for using fragments
If you have experience with fragments, you must have a constructor that takes an empty parameter. Otherwise, the Fragment will compile with an error:
This fragment should provide a default constructor (a public constructor with no arguments)
Copy the code
But even if we add null constructor, if we define any constructor with parameters, we will still be reminded kindly:
Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]
Copy the code
So Android doesn’t want Framgent to carry construction parameters.
If the Fragment is restored after a Configuration Change (such as vertical and vertical rotation), the system does not know which constructor to select, so the system and developer agree to use the default null constructor and then set the initialization value with setaracknowledgments.
Previous treatment: static factory
To do this, a common practice is to avoid using constructors with arguments directly by using static methods.
The static getInstance(String STR) method constructs the Fragment with an empty parameter and then initializes it with setarnames.
public class MainFragment extends BaseFragment {
private static final String MY_ARG = "my_arg";
private String arg = "";
public static MainFragment getInstance(String str) {
MainFragment fragment = new MainFragment();
Bundle bundle = new Bundle();
bundle.putString(MY_ARG, str);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getArguments() ! =null) { arg = getArguments().getString(MY_ARG); }}}Copy the code
You can then use this static method to build the Fragment
MainFragment fragment = MainFragment.getInstance("Hello world!!");
Copy the code
The static method fragment.instantiate (between onCreate and onActivityCreated) will be called during the Fragment restore rebuild
@NonNull
public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
@Nullable Bundle args) {
try {
Class extends Fragment> clazz = FragmentFactory.loadFragmentClass(
context.getClassLoader(), fname);
Fragment f = clazz.getConstructor().newInstance();
if(args ! =null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
return f;
} catch (java.lang.InstantiationException e) {
Copy the code
The bundle we passed earlier with setArguments (saved with onSaveInstanceState) will be passed to instantiate by the system to assist with the fragment recovery rebuild.
New solution: FragmentFactory
More than empty of fragments and the stipulations of the constructor, as androidx. Fragments: fragments – 1.1.0 – alpha01 publishing history.
Fragment.instantiate has been Deprecated in the new version . It is recommended to use FragmentManager getFragmentFactory and FragmentFactory. Instantiate (this, String). FragmentFactory allows developers to define their constructors as they want, rather than having to construct null parameters.
2. How to use FragmentFactory?
Assuming that our MainFragment needs two parameters, how do we construct using FragmentFactory?
Define FragmentFactory
First, you need to define your own FragmentFactory. Instantiate method instantiate () ¶ Instantiate method instantiate () ¶ Even if you want to use the bundle pass parameter, it is recommended that you set it manually here, rather than using the system Settings.
class MyFragmentFactory extends FragmentFactory {
private final AnyArg anyArg1;
private final AnyArg anyArg2;
public MyFragmentFactory(AnyArg arg1, AnyArg arg2) {
this.anyArg1 = arg1;
this.anyArg2 = arg2;
}
@NonNull
@Override
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
Class extends Fragment> clazz = loadFragmentClass(classLoader, className);
if (clazz == MainFragment.class) {
return new MainFragment(anyArg1, anyArg2);
} else {
return super.instantiate(classLoader, className); }}}Copy the code
Use the Framgent constructor for the FragmentFactory function:
protected MainFragment(AnyArg arg1, AnyArg arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
}
Copy the code
Set up the Factory
The next step is to set this Factory for the FragmentManager in the Activity’s onCreate
MyFragmentFactory fragmentFactory = new MyFragmentFactory( someObject1, someObject2);
@Override
public void onCreate(Bundle savedInstanceState) {
getSupportFragmentManager().setFragmentFactory(fragmentFactory);
super.onCreate(savedInstanceState);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
.replace(
R.id.fragment_container,
MainFragment.class);
if (addToBackStack) {
fragmentTransaction.addToBackStack(tag);
}
fragmentTransaction.commit();
}
Copy the code
The FragmentManager will use this factory to create instances when creating or restoring fragments.
It is important to note that setFragmentFactory must be called before super.onCreate, because super.onCreate will be used for fragment reconstruction.
3. Application scenario: Set LayoutId
Androidx. Annotation: the annotation – 1.1.0 – alpha01 up introduces @ ContentView annotation is used to set the default layout file for fragments, but soon after, Androidx. Fragments: fragments – 1.1.0 – alpha05, @ ContentView from class notes to the constructor annotations, fragments much a constructor with parameters: support use tectonic setting LayoutId:
/**
* 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 // An annotation used to be used on a Class
public Fragment(@LayoutRes int contentLayoutId) {
this(a); mContentLayoutId = contentLayoutId; }Copy the code
Note: Activity since androidx. Activity: Activity – 1.0.0 – alpha06 establish LayoutId also support through the constructor
The constructor stores the passed LayoutId in mConentLayoutId. The onCreateView automatically creates a ContentView based on mConentLayoutId:
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if(mContentLayoutId ! =0) {
return inflater.inflate(mContentLayoutId, container, false);
}
return null;
}
Copy the code
So if you use the constructor to set the LayoutId, you don’t have to override the onCreateView.
Fragment(@LayoutRes int contentLayoutId) + FragmentFactory
What does this have to do with FragmentFactory, you might ask?
When ConfigurationChanged happens, the default constructor is called to restore the fragment. The mContentLayoutId information is lost and the onCreateView cannot create the view.
Therefore, when using the constructor to set the LayoutId, you must set a matching FragmentFactory if you want to consider a recovery and reconstruction scenario. Maybe there are too many potholes, which was highlighted in javadoc after 1.1.0:
You must 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.
So, all in all, do you think it’s convenient to set LayoutId through the constructor?
4. Application scenario: Dependency injection
FragmentFactory allows you to create fragments with custom build parameters, which can be useful in scenarios where DI frameworks like dagger and KOin are used.
Take the use of the FragmentFactory in Koin as an example (Koin basics are not covered):
/ / define fragmentModules
private val fragmentModules = module {
fragment { HomeFragment() }
fragment { DetailsFragment(get()}// Get the dependent parameters with get()
}
private val viewModelsModule = module {
viewModel { DetailsViewModel(get()}}/ / start Koin
override fun onCreate(a) {
super.onCreate()
startKoin {
androidContext(this@App)
fragmentFactory() / / add KoinFragmentFactoryloadKoinModules(listOf(viewModdules, fragmentModules, ...) )/ / in fragmentModules}}Copy the code
As above, the DetailsFrament parameter depends on the DetailsViewModel
KoinFragmentFactory
Koin injects this parameter dependency into it using the KoinFragmentFactory, which is essentially a FragmentFactory
class KoinFragmentFactory : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val clazz = Class.forName(className).kotlin
val instance = getKoin().getOrNull<Fragment>(clazz) // Create fragments using Koin
returninstance ? :super.instantiate(classLoader, className)
}
}
Copy the code
Koin creates fragments using the KoinFragmentFactory. The constructor allows arguments, which can be obtained through Koin’s dependency injection
The FragmentFactory needs to be set to use in the FragmentManager. The same goes for KoinFragmentFactory. You need to call setupKoinFragmentFactory() in Activity#onCreate or Fragment#onCreate to add it to the current FragmentManager
override fun onCreate(savedInstanceState: Bundle?). {
setupKoinFragmentFactory() // Set to FragmentManager
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
Copy the code
fun FragmentActivity.setupKoinFragmentFactory(scope: Scope? = null) {
if (scope == null) {
supportFragmentManager.fragmentFactory = get()}else {
supportFragmentManager.fragmentFactory = KoinFragmentFactory(scope)
}
}
Copy the code
Note that this call must be made before super.onCreate, because super.onCreate is where the fragment is rebuilt and where the FragmentFactory is needed
Once the Koin configuration is complete, you can add DetailFagment to the Activity as usual
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailsFragment::class.java, null)
.commit()
Copy the code
FragmentTransaction automatically call KoinFragmentFactory# instantiate () create DetailsFragment: : class. Java corresponding fragments, very convenient?