Zhihu’s single Activity+ multiple Fragment client is really smooth as a silk stocking version when used, and provides the zhihu team with a pen core. However, there are various problems with the Fragment during use, and it is difficult to use it normally. It is difficult to write such a client without vomiting blood.

This article introduces a FragmentRigger management framework for fragments. The framework has only two goals: 1. Make fragments easier to use. 2. Use the cheapest Fragment framework. This article first explains the background of the framework, then introduces the problems the framework solves and gives some solutions, and finally introduces the use of the framework (Water Star trilogy).

First, throw the bait

Question 1: Why reinvent wheels when there aren’t dozens of Fragments on the Web, you might ask?

Yes, there are many frameworks about fragments on the Internet, such as Fragmentation Fragmentation from the well-known YoKeyword, which solves the Fragment problem in various scenarios and adds additional support such as left slide exit. It is not too powerful. Please accept my knee.

Question 2: please answer question 1 head-on. Is there anything different about this framework? Old farmers? Repeat wheel idle egg pain?

Most Fragment frameworks on the web include a parent Fragment and Activity class and add method support, so using those frameworks requires your Activity and Fragment to inherit the parent class provided by their framework (somehow, I’ve always been a bit averse to inheriting other people’s superclasses). Yes, many Fragment operations are life-cycle dependent, so it’s technically impossible to manage a Fragment without inheriting its parent class, but FragmentRigger lets you integrate with a Fragment without having to inherit any classes!! (Of course, you still need to inherit your own parent class.)

Question three: What? Do not inherit?? How does it work? Is it more complicated?

Complicated?? The purpose of this framework is to make it easier to use fragments.

// Add and show BFragment in the Activity.
@Puppet(containerViewId = R.id.container)
public class AActivity extend AppcompatActivity{
    // Trigger the display operation
    Rigger.getRigger(this).startFragment(BFragment.newInstance());
}
Copy the code

Did not deceive you, the above code has no inheritance, call a line of code, cost only a line of annotations can be used!!

Question four: so little code, also do not inherit, rely on unreliable ah?

The purpose of this framework is to make it easier to use fragments. The reason for not inheriting fragments is that there are a lot of people who don’t want to use a third party’s parent class, and I am no exception. Even though I know there is nothing important in it, I still feel extremely insecure. Use AOP to define Activity/Fragment lifecycle and other methods as pointcuts and insert them into proxy classes.

Earn trust

This section will list the problems encountered and give some solutions to some of them, so you should be at ease!! I’m not just here to cheat Star!!

1. Common difficulties

What are some of the things that cause us headaches when using fragments, because we often get errors when using fragments, and some of the scenarios are hard to implement? Let’s list them one by one.

Difficulty 1: In the use of the process encountered maddening exceptions thrown!!

  • Can not perform this action after onSaveInstanceState
  • Can not perform this action inside of
  • Activity has been destroyed
  • FragmentManager is already executing transactions

Difficulty 2: Errors encountered when using Fragment itself

  • GetActivity () returns null
  • Remove a Fragment after the transition animation does not execute problem
  • Various issues with Fragment stacks
  • Fragment layer nesting problem
  • Fragment overlapping display
  • Fault encountered after the screen is flipped (memory is restarted)
  • Add two fragments to a ContainerView. The first Fragment can also be clicked
  • Problems caused by things not being executed immediately after they are submitted

Difficulty three: Other problems

  • Unable to listen on onBackPressed
  • Use lazy loading in ViewPager
  • Fragment multi-layer nesting in and out of the stack problem
  • Fragment transaction submission failed
  • Multiple fragments are being pushed/removed simultaneously

The above is only part of the problems encountered in using Fragment, all kinds of evil behavior, too numerous to record!! But these are all fixed in FragmentRigger!!

2. Solutions

So how are these problems solved? Due to chapter limitations, here are some solutions to particularly common problems.

Can not perform this action after onSaveInstanceState

This exception is thrown in the FragmentManager, and the source code is as follows:

private void checkStateLoss(a) {
    if (mStateSaved) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState"); }}Copy the code

This method is called in the enqueueAction(Runnable, Boolean) method.

 public void enqueueAction(Runnable action, boolean allowStateLoss) {
    if (!allowStateLoss) {
        checkStateLoss();
    }
}
Copy the code

This method is called when something is committed, and the parameters are passed at that time, so using the commitAllowingStateLoss method does avoid throwing this exception, but the commit may be lost, so it’s not the best solution. Using this method is to avoid exceptions, not to solve them!!

So to resolve this exception, we need to know when the mStateSaved method is set to true. After analyzing the source code (please do your own analysis and do not elaborate on the process here), we find that mStateSaved is set to true when Activity#onStop is called. OnSaveInstanceState is called before onStop, so this error is valid. The onSaveInstanceState method in the Activity lifecycle is preceded by the onPause method, so we just need to determine if the onPause has been executed and do not commit the thing when it has already been executed. Thankfully, the Fragment provides a method isResumed() to determine that state, which you can implement manually in your Activity. The final solution is: don’t submit your Activity/Fragment while it’s not onResume, save it, and resubmit it with onResum to ensure it’s successfully submitted and not lost!!

Resolved: Overlapping Fragment display

Multiple fragments are added to the same container in the “show” state, causing them to overlap. This solution was addressed in YoKeyword’s article “9 Lines of Code to Say Goodbye to Overlapping Fragments in Your App” and won’t be repeated here.

Resolved: cannot listen on onBackPressed

This is a problem most people have, but there is no support for this method in the Fragment, so we have to implement this function manually. Solution: Define the method onBackPressed in the Fragment and call the method through the held Fragment in the Activity. Everything seems simple, but there are a series of new problems, such as: the order of delivery after multi-level nesting of fragments on the stack, the interception of the method on the stack, etc. It’s very expensive to implement. However, in FragmenTRigger this problem is properly addressed.

Fixed: Lazy loading in ViewPager

ViewPager provides us with a mechanism for preloading, but this mechanism can sometimes be a bad thing to use. If we set fewer entries through setOffscreenPageLimit, it will cause the Fragment instance to be regenerated during the switch. However, adding too many fragments will cause many fragments to be initialized at the same time, so lazy loading can effectively handle this scenario, loading data only when it is displayed, and normally only once. So how do we add lazy loading to ViewPager? SetUserVisibleHint (Boolean) specifies whether or not the Fragment is displayed. The ViewPager can override this method in the Fragment and determine whether or not the Fragment is displayed based on the Boolean value passed in. Note that setUserVisibleHint will be called every time the ViewPager switches if the Fragment is lazy or not. The solution is to override the setUserVisibleHint() method in your Fragment and define a lazy-loaded method such as onLazyLoad(), determine whether your Fragment is displayed based on the value passed in by setUserVisibleHint(), and call the lazy-loaded method. The sample code is as follows:

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
  if(! mHasInitView||! isVisibleToUser)return;
  //make sure the method onLazyViewCreated will be called only once.
  if (mHasInvokeLazyLoad) return;
  onLazyLoad();
}
Copy the code

Of course, that’s just pseudocode, but that’s how lazy loading works.

Third, on the hook

FragmentRigger: FragmentRigger: FragmentRigger: FragmentRigger: FragmentRigger: FragmentRigger: FragmentRigger: FragmentRigger: FragmentRigger: FragmentRigger

A powerful Fragment framework that aims to make using fragments easier!!








This is probably the cheapest Fragment framework to use. No need to inherit!! No need to inherit!! No need to inherit!! Say three important things!! When using FragmentRigger, use only one line of comment!! The idea is to define Fragment/Activity life-cycle related methods as pointcuts that operate through ASpectJ bindings and using proxy classes.

1.Wiki

  • The installation
  • Begin to use
  • Fragments of manipulation
  • Lazy loading
  • Animated transitions
  • OnBackPressed intercept
  • StartFragmentForResult method
  • How to use it in library Module
  • Version of the log

2, features,

  • Super powerful Api support
  • Plenty of English notes
  • Strict exception throws
  • Resolve common exceptions and bugs in fragments
  • Transaction commits are never lost
  • Extend native methods, addonBackPressedAnd other common methods
  • Current stack member tree print
  • Fragments lazy loading
  • Fragment Transition animation
  • Fragment Sharing element Transition animation (TODO)
  • Kotlin Support (TODO)

3. Solved problems

  • Fragment interface overlap
  • Fragment Indicates multiple levels of nesting
  • Fragment stack management issues
  • Fragment transaction submission failed. Procedure
  • The Activity submits a transaction in a non-onResume state
  • The Fragment transaction commit cannot be executed immediately, causing two commit events to conflict
  • Memory restartIs a series of anomalies
  • Save and restore the data when the screen is flipped
  • Can not perform this action after onSaveInstanceState
  • Lazy loading in ViewPager and other scenarios
  • Different scenes under the transition animation does not execute the problem

4. Demo

Stack management Lazy loading Display at the same level
Support Fragment level \ multi-layer nesting, and provide a series of scenarios support to return to the top of the stack automatically display members supportViewPagerScenarios such as lazy loading mechanism, simple to use, a line of annotations can support throughshowMethods according toFragmentSupports preloading and lazy loading scenarios

To keep the chapter simple and beautiful, please check the details of other scenes in the project!!

5, super powerful Api support demo

The framework claims strong Api support from the start, so this section illustrates a few scenarios.

Scenario 1: Lazy loading of fragments

In front of the lazy loading also put forward the corresponding solution, so how to use it in this framework? Take a look at the following code:

@LazyLoad
@Puppet
public class ContainerFragment extends Fragment{
  public void onLazyLoadViewCreated(Bundle savedInstanceState) {
    //do something in here}}Copy the code

Usage cost: two lines of comments, one method, no need to inherit from parent class!!

Scene two: The transition animation

Fragment provides us with a transition animation mechanism, but it needs to be used in conjunction with the submission of things, and it does not support transitions when removing things.

@Animator(enter=R.anim.enter,exit=R.anim.exit,popEnter=R.anim.popEnter,popExit=R.anim.popExit)
@Puppet
public class AnimatorFragment extends Fragment{}Copy the code

Usage cost: two lines of comments, one method, no need to inherit from parent class!! The problem is how to use this annotation in the library. Because resource ids in R in the library are variables, they cannot be used directly in annotations. This framework also provides a corresponding solution.

@Puppet
public class AnimatorFragment extends Fragment{
  public int[] getPuppetAnimations(){
    return new int[]{
        R.anim.enter, R.anim.exit, 0.0}; }}Copy the code

It does not need to support a scene transition animation to be set to 0, but the return argument must be an int array of length 4. No need to inherit, just add the method!!

Scenario 3: Stack management

This framework completely abandoned the original stack, internal maintenance of the stack management!! So how do you open a Fragment for pushing? Take a look at the following code:

@Puppet(containerViewId = R.id.container)
public class AActivity extend AppcompatActivity{
    // Trigger the display operation
    Rigger.getRigger(this).startFragment(BFragment.newInstance());
}
Copy the code

Usage cost: one line of comment, one line of code, no need to inherit parent class!! This framework even provides the stack tree diagram printing, you can view the internal stack members in real time!! By default, the member at the top of the stack is displayed when the stack is unloaded, no additional display operation is required, and onBackPress is called in the stack member and supports any level of interception!!

Scenario 4: onBackPressed and its interception

This framework provides support for onBackPressed methods for in-stack fragments!! And support any level of interception, transmission order from outside to inside!!

@Puppet
public class StackFragment extends Fragment{
  public void onRiggerBackPressed(a){
    //Rigger.getRigger(this).onBackPressed();
    // No interception No need to write this method, if there is a method can be intercepted in this method, the uplink code is called the default return code.}}Copy the code

Usage cost: one line annotation, one method, no need to inherit parent class!! If you need to call the default return method, use rigger.getrigger (this).onbackpressed ().

The above scenarios support some of the ways to use the frameworkWiki

6. Open source agreements

This project is under the MIT Open Source license. View LICENSE for more information.