Reference: Android Fragments Doc
In 2021, it’s clear how fragments are created, loaded, and even common craters. This article will not discuss these, but will hopefully discuss some better ways to use fragments.
Introduction to the
For our introduction to Fragments, let’s start with a few questions:
What is Fragment? What does Fragment do? What are the advantages of using Fragment?
So let’s answer them one by one:
What is Fragment?
Fragment represents a reusable part of an application interface. Fragments define and manage their own layout, have their own life cycle, and can handle their own input events. A Fragment cannot exist on its own, but must be hosted by an Activity or another Fragment. The Fragment view hierarchy becomes part of, or is attached to, the host view hierarchy.
The above is the official definition given by Google. My personal understanding is that it is a “fragment” of Activity used to distinguish different functions. We all know the five principles of object orientation, and Android applications should also follow these principles, but considering that mobile applications are directly user-oriented, So there’s always a page filled with all sorts of functions that have no connection at all.
In this case, our code should also follow the principle of “high aggregation, low coupling”, that is, one feature on a page should focus on itself and be insensitive to other features; In addition, the page itself (that is, the Activity) should have minimal understanding of the functionality in order to increase the flexibility and extensibility of the code.
What does Fragment do?
As mentioned above, I think Fragment is used to decouple parts of a page that have different functions. Android officially provides a best practice to avoid having to define your own framework to separate these functions.
What are the advantages of Fragment?
As mentioned in the answers to the two questions above,
First, fragments have their own life cycle, giving you more control over the specific logic of the Fragment depending on its life cycle.
Secondly, Fragment is officially supported, which means that it will be more and more stable with Android version updates. On the other hand, it shows that it is a universal “component”. You can assemble different fragments into one page, or remove them at any time, and easily make them work in another page.
Also, fragments are “light” and you can use them to implement a “single Activity architecture”;
Finally, as fragments were created for, they are perfect for decoupling different functions in your App, providing great flexibility and reusability.
How to use Fragments gracefully?
As Fragment means “Fragment”, we should use multiple fragments to put a page together.
Let’s pick a complex page to illustrate the advantages of Fragments.Let’s break down the structure of this page briefly:
If we implemented it directly without thinking about it, the page would look something like this:
- The top two tabs, ViewPager + Fragment.
- Underneath the scrolling page, a RecyclerView (or NestedScrollView + RecyclerView) all done.
There’s nothing to say about this part. Everyone does it. , let’s do a simple pseudo-code simulation.
/** * dynamic */
public class MomentsFragment extends Fragment {
/** * top video and integrated Tab */
private ViewPager tabPager;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// return inflater.inflate()...
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
tabPager = view.findViewById(R.id.view_pager);
FragmentPagerAdapter adapter = new MomentsFragmentAdaper();
/ / video Tab
adapter.addFragment(new VideoFragment());
/ / Tab
adapter.addFragment(newCommonFragment()); tabPager.setAdapter(adapter); }}Copy the code
That’s about it. As for the part of 2…
We can of course do what we said above, and put all the content into the Fragment, leaving 1. Use all the Adapter types, 2. Or write several views, addView, 3. Or you can customize a container to decouple it a little bit, and then the container provides getView, and then addView.
But consider the consequences: 1. If we put it in a Adapter, we would get a 3000 line Adapter. Each part of the page might be just a method, with network requests and the code to parse network requests. But it’s likely that a month from now we’ll have to look at all 3,000 lines of code just because the product manager wants us to move the “most visited” and “channels & Topics” sections.
Above everyone when fun to see the line, we are all old river’s lake, such code even if written also can not pass Review this pass. Here is also a simple code:
public class VideoPageAdapter extends RecyclerView.Adapter {
private static final int TYPE_VIDEO = 0;
private static final int TYPE_SEARCH = 1;
private static final int TYPE_RECENT = 2;
private static final int TYPE_CHANNEL = 3;
@Override
public int getItemViewType(int position) {
int type = TYPE_VIDEO;
switch (position) {
case 0:
type = TYPE_SEARCH;
break;
case 1:
type = TYPE_RECENT;
break;
case 2:
type = TYPE_CHANNEL;
break;
}
return type;
}
// Omit three thousand lines here
}
Copy the code
Now, what about 2 and 3? As anyone with a bit of experience can imagine, we should decouple the parts that represent different functions, at least into different classes — we could use different views to represent different functions, but considering MVP, MVVM, etc., using a View would be a bit of a boghole for the future. We could then customize a container to act as a Controller or Presenter to perform different functions…
So, instead of building our own wheels, why not use fragments? If, instead of a Fragment, we create a custom Container or Presenter, we need to hold all of their references in the outermost Fragment, and then we have to have different lifetimes. Manually calling methods triggers their LifeCycle (of course you can do this more elegantly with JetPack’s LifeCycle). As for the different containers, if we wanted them to communicate with each other, the worst thing that could happen is that they would have to hold references to each other and the code would still be a mess…
Here is another example of pseudo-code (error demonstration) :
/** * video Tab */
public class VideoFragment extends Fragment {
private ContainerOrPresenter searchPresenter;
private ContainerOrPresenter recentPresenter;
private ContainerOrPresenter channelPresenter;
private ContainerOrPresenter videoPresenter;
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
searchPresenter = new ContainerOrPresenter();
recentPresenter = new ContainerOrPresenter();
// Suppose two presenters need to communicate with each other.
searchPresenter.setRecentPresenter(recentPresenter);
}
@Override
public void onResume(a) {
super.onResume();
// Simulate tedious life cycle control
searchPresenter.onResume(this);
recentPresenter.onResume(this);
channelPresenter.onResume(this);
videoPresenter.onResume(this); }}Copy the code
The problem with this code is that we maintain a completely unnecessary life cycle and have to hold on to redundant references.
So use Fragment! We have the authorities to maintain the lifecycle for us, and they provide the best practices of Fragment + ViewModel so that we can transfer data between different parts. We can implement the MVVM architecture with little effort, and if we want to reuse or remove some of the functionality in the future, A simple Fragment operation will do. This is where fragments have the advantage of decoupling and their own life cycle.
So, going back to the original question, a better way to implement this page after we’ve added fragments might look like this…
- The top two tabs, ViewPager + Fragment.
- NestedScrollView + 4 fragments
It goes something like this:
We don’t need to care about what the Fragment inside the scrolling page is. We just need to simply add the outer Fragment() and new Fragment() as needed. We can put all the functions of different parts into different fragments. Different fragments can communicate with each other through the ViewModel.
This idea works for almost any page.
First of all, we made some changes to the outermost Fragment that represents the “dynamic” Fragment, mainly by adding a ViewModel, as well as some differences in the way the sub-fragments are created (we’ll mention this later), which are detailed in the comments:
/** * dynamic Tab */
public class MomentsFragment extends Fragment {
/** * top video and integrated Tab */
private ViewPager tabPager;
private MomentsViewModel momentsViewModel;
private FragmentActivity hostActivity;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
hostActivity = (FragmentActivity) context;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The "of" method can be a Fragment or an Activity
// If the Fragment needs to communicate with the Activity or with other Fragments under the Activity, it is recommended to pass the Activity as a parameter
// It is worth noting that the ViewModel passed into the Activity to create persists until the Activity is destroyed, so watch out for memory leaks
momentsViewModel = ViewModelProviders.of(hostActivity).get(MomentsViewModel.class);
momentsViewModel.newMessageLiveData.observe(this.new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
// The number of new messages in the upper right corner of the "dynamic" TAB is returned}}); }@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// return inflater.inflate(res, container, false);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
tabPager = view.findViewById(R.id.view_pager);
// This should be getChildFragmentManager, without too much explanation
MomentsFragmentAdapter adapter = new MomentsFragmentAdapter(
getChildFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
List<String> childList = new ArrayList<>();
// Video Tab, here are some changes from the original, explained later
// It is also easy to see that this is decoupled from passing in the Fragment instance directly
// We don't need to pay attention to what is in Moments, just pass in the path of the page
childList.add(Routes.ROUTE_VIDEO);
/ / Tab
childList.add(Routes.ROUTE_COMMON);
adapter.setDataList(childList);
tabPager.setAdapter(adapter);
}
@Override
public void onResume(a) {
super.onResume();
// A network request is made
momentsViewModel.requestNewMessage();
}
@Override
public void onDestroy(a) {
super.onDestroy();
// Avoid memory leaks
momentsViewModel.newMessageLiveData.removeObservers(this); }}Copy the code
Strictly speaking, the code for the “dynamic” page should do just fine. If there are more details to be refined, the most we can do is change some OF the UI-related things (here we put the number of new Tab messages in the request is just a simulation, in reality the Tab may be outside the Fragment, so we can’t do this). Subsequent additions to the relevant logic will also focus on the ViewModel, avoiding changes to the Fragment itself.
Incidentally, we mentioned the decoupling effect of ARouter on fragments here. Here’s a quick demonstration of what ARouter+Fragment can do.
As you can see, we don’t even need the specific Fragment anymore. The original implementation method is that we don’t need to know which Fragment we need, but we still need to manually create new XXXFragment. After using ARouter, We just need the paths corresponding to different fragments.
Now, with the post office, Joe can put his letter in the mailbox and deliver it directly to Joe without having to go there himself.
Let’s move on to the next level of the dynamic home page, dynamic-Synthesis
The page itself is a container. We just need to fill in the contents of the container. There is no need to implement any additional logic by ourselves.
/** * dynamic - composite * container Fragment, used to hold the specific search, recent access, channel, dynamic list submodules */
@Route(path = Routes.ROUTE_COMMON)
public class CommonFragment extends Fragment {
private CommonViewModel commonViewModel;
private FragmentActivity hostActivity;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
hostActivity = (FragmentActivity) context;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The "of" method can be a Fragment or an Activity
// If the Fragment needs to communicate with the Activity or with other Fragments under the Activity, it is recommended to pass the Activity as a parameter
// It is worth noting that the ViewModel passed into the Activity to create persists until the Activity is destroyed, so watch out for memory leaks
commonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Suppose the LinearLayout is wrapped inside NestedScrollView. Suppose it's called "ll_container".
// Using ARouter to create a Fragment, we only need four paths to complete the Fragment
FragmentManager manager = getChildFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_SEARCH), Routes.ROUTE_SEARCH);
transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_RECENT), Routes.ROUTE_RECENT);
transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_CHANNEL), Routes.ROUTE_CHANNEL);
transaction.add(R.id.ll_container, createFragmentByPath(Routes.ROUTE_VIDEO_LIST), Routes.ROUTE_VIDEO_LIST);
transaction.commit();
}
private Fragment createFragmentByPath(String path) {
return(Fragment) ARouter.getInstance().build(path).navigation(); }}Copy the code
The ViewModel is created to communicate between the parent Fragment and all of its child fragments, but more on that later.
Implement channel Fragment and video list Fragment.
/** * dynamic - comprehensive page - channel */
@Route(path = Routes.ROUTE_CHANNEL)
public class ChannelFragment extends Fragment {
private CommonViewModel commonViewModel;
private RecyclerView channelRecyclerView;
private RecyclerView.Adapter channelAdapter;
private FragmentActivity hostActivity;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
hostActivity = (FragmentActivity) context;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
commonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
commonViewModel.channelData.observe(this.new Observer<List<ChannelData>>() {
@Override
public void onChanged(List<ChannelData> channelData) {
if (null! = channelData) {// channelAdapter.setDataList(channelData);channelAdapter.notifyDataSetChanged(); }}}); }@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// channelRecyclerView = view.findViewById(R.id.rv);
// channelAdapter = new ChannelAdapter();
channelRecyclerView.setLayoutManager(new GridLayoutManager(hostActivity, 2));
channelRecyclerView.setAdapter(channelAdapter);
}
@Override
public void onResume(a) {
super.onResume();
commonViewModel.requestChannel();
}
@Override
public void onDestroy(a) {
super.onDestroy();
commonViewModel.channelData.removeObservers(this); }}Copy the code
/** * Dynamic - comprehensive page - Video list */
@Route(path = Routes.ROUTE_VIDEO_LIST)
public class VideoListFragment extends Fragment {
private CommonViewModel commonViewModel;
private RecyclerView videoRecyclerView;
private RecyclerView.Adapter videoAdapter;
private FragmentActivity hostActivity;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
hostActivity = (FragmentActivity) context;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
commonViewModel = ViewModelProviders.of(this).get(CommonViewModel.class);
commonViewModel.channelData.observe(this.new Observer<List<ChannelData>>() {
@Override
public void onChanged(List<ChannelData> channelData) {
if (null! = channelData) {// channelAdapter.setDataList(channelData);videoAdapter.notifyDataSetChanged(); }}}); }@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// videoRecyclerView = view.findViewById(R.id.rv);
// videoAdapter = new VideoAdapter();
videoRecyclerView.setLayoutManager(new LinearLayoutManager(hostActivity));
videoRecyclerView.setAdapter(videoAdapter);
}
@Override
public void onResume(a) {
super.onResume();
commonViewModel.requestVideo();
}
@Override
public void onDestroy(a) {
super.onDestroy();
commonViewModel.videoData.removeObservers(this); }}Copy the code
It can be seen that we have gradually changed from a single category responsible for all functions to a category that only serves as containers. Functions are refined and divided into different child fragments to improve flexibility and reusability.
Another benefit of this is that different pages can now be developed by different people, avoiding conflicts and increasing efficiency.
Fragments and ARouter
Project address: github.com/alibaba/ARo…
We mentioned above that Fragment + ARouter is a good practice.
I’m not going to say much about ARouter, but if you haven’t used it, you can Google it.
This itself is a routing framework used to achieve communication between different modules in the modularization, which reduces the dependency between different activities in Android and simplifies the relationship between different routing addresses, so as to achieve decoupling.
The same idea applies to fragments that decouple them. (Of course, ARouter has a different way of using activities and fragments, which is described in detail in the code.)
Fragment + ARouter
In my opinion, the benefits are as follows:
- The decoupling
- Parameter transfer optimization & dependency injection
- Through path unification to achieve multi-terminal unification of the page
First, needless to say, we wanted to create a Fragment and needed to know the name of the class. Now we just need to know the routing address path.
We all know that adding parameters to the Fragment constructor to pass parameters is a sinkhole, which is a classic source of Crash. Using ARouter can help simplify a lot of work.
The third point is that when the page and path correspond, we can go through a path to the same page on Android, ios, and even H5. In theory, if the server sends a set of such path configuration, we can combine the same page through different styles, or easily realize the different TAB on the home page. For example, server path can be configured on the corresponding page of the path popup or advertising, suspension window, very flexible. Finally, a page can correspond to a native page and H5 page respectively. If the native page path is not found, the path (we can assemble it into a URL) can be directly degraded to open a Web page.
Here is a brief description, when the opportunity can be supplemented with code.
Fragments and ViewModel
Fragment + ViewModel Has two advantages:
- Implement communication between fragments and activities and between fragments.
- Easily implement the MVVM pattern.
The second point can be started in another article, but I won’t go into it here. On the first point, this is also how Google’s official recommendation is implemented today:
Developer. The android. Google. Cn/guide/fragm…
Instead of writing it myself, HERE are some excerpts from official sources:
The ViewModel is ideal when you need to share data between multiple fragments or between the Fragment and its host Activity. ViewModel objects store and manage interface data. For a detailed view of the ViewModel, see the ViewModel Overview.
Both Fragment and its host Activity can use the Activity scope to retrieve a shared instance of the ViewModel by passing the Activity into the ViewModelProvider constructor. The ViewModelProvider is responsible for instantiating the ViewModel or retrieving it, if it already exists. Both components can observe and modify this data
Two or more fragments in the same Activity usually need to communicate with each other. For example, suppose you have one Fragment that displays a list, and another Fragment that allows users to apply various filters to that list. This may not be easy if fragments do not communicate directly (meaning, they are no longer independent). In addition, both fragments must also deal with cases where the other Fragment has not yet been created or is not visible.
Viewmodelproviders.of (hostActivity).get(commonViewModel.class); As long as the instance of “of” is passed in is the same, then we get the same ViewModel for this method.
The two fragments can handle this communication by sharing the ViewModel with their Activity scope. By sharing viewModels in this way, fragments don’t need to know each other, and activities don’t need to do anything to facilitate communication.
Again, we just need viewModelprovider.of (getParentFragment()).get(commonViewModel.class); As long as the instance of “of” is passed in is the same, then we get the same ViewModel for this method.