An introduction to FragmentArgs a lightweight development kit in Android that writes parameters to fragment

  • 2020-06-12 10:34:08
  • OfStack

Android development can sometimes be a headache. You have to write a lot of code for something as simple as setting up fragment. Fortunately, java supports one powerful tool: the annotation processor (Annotation Processors).

The problem with Fragment is that you have to set a lot of parameters to make it work. Many new android developers usually write something like this:


public class MyFragment extends Fragment
{
private int id;
private String title; public static MyFragment newInstance(int id, String title)
{
MyFragment f = new MyFragment();
f.id = id;
f.title = title;
return f;
} @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Toast.makeText(getActivity(), "Hello " + title.substring(0, 3),
Toast.LENGTH_SHORT).show();
}
}

What's wrong with that? I've tried it on my own device, and it works, right?

It works, but have you tried changing your device from portrait to landscape? Your app will crash because of NullPointerException when you try to access id or title.

My app is normal because I set app to portrait. So I've never had this problem.

Whatever you! Android is a true multitasking operating system. Multiple app runs at the same time, and the android system will destroy activity (and the fragment it contains) if memory is needed. You may not notice these issues in your daily app development. However, when you release with play store, you will notice that your app crashes, but you don't know why. Users of your app may be using more than one app at a time, and it is likely that your app was destroyed in the background. For example: A user opens your app and MyFragment displays on the screen. Next, your user presses the home key (which is your app running in the background) and opens other applications. Your app may be destroyed by freeing up memory. After that, the user returns to your app, for example via the multitasking button. So what will Android do now? Android will revert to the previous app state, and MyFragment will be restored, and that's the problem. Fragment tried to access title, but title is null because it is not permanently preserved.

I see, so I need to save them in onSaveInstanceState(Bundle)?

It isn't. The official documentation is a little unclear, but onSaveInstanceState(Bundle) should be used the same way you use ES61en.onSaveInstanceState (Bundle) : You use this method to save the "temporary" state of the instance, for example, to work with the orientation of the screen (vertical to horizontal, and vice versa). So when app is killed in the background the instance state of fragment is not saved as persistent data, it is used to restore the data when it returns to the foreground one more time. It does what ES66en.onSaveInstanceState (Bundle) does in Activity, and they are used to "temporarily" hold the state of the instance. However, persistent parameters are transmitted via external intent data.

So I should have Intent in Activity to save the Fragment argument?

No, Fragment has its own mechanism. There are two methods: Fragment.setArguments (Bundle) and Fragment.getArguments (), which you must use to ensure that the parameters are persisted. That's the pain I mentioned above. You need a lot of code to do this. First, you create an Bundle, then you put in the key-value pairs, and finally you call Fragment.setArguments (). Unfortunately, your work is not finished and you have to read Bundle by Fragment.getArguments (). 1 Some of these jobs:


public class MyFragment extends Fragment
{
private static String KEY_ID = "key.id";
private static String KEY_TITLE = "key.title";
private int id;
private String title; public static MyFragment newInstance(int id, String title)
{
MyFragment f = new MyFragment();
Bundle b = new Bundle();
b.putInt(KEY_ID, id);
b.putString(KEY_TITLE, title);
f.setArguments(b);
return f;
} @Override
public void onCreate(Bundle savedInstanceState)
{
// onCreate it's a good point to read the arguments
Bundle b = getArguments();
this.id = b.getInt(KEY_ID);
this.title = b.getString(KEY_TITLE);
} @Override
public View onCreate(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
// No NullPointer here, because onCreate() is called before this
Toast.makeText(getActivity(), "Hello " + title.substring(0, 3),
Toast.LENGTH_SHORT).show();
}
}

I hope you understand by now what I mean by "pain." You will be writing a lot of code for each fragment in your application. If someone writes this code for you, it's not going to be satisfying. Annotation processing allows you to generate java code at compile time. Note that we are not talking about commenting on the use of reflection at run time.

FragmentArgs is a lightweight package for generating accurate java code for your fragment.


import com.hannesdorfmann.fragmentargs.FragmentArgs;
import com.hannesdorfmann.fragmentargs.annotation.Arg; public class MyFragment extends Fragment
{
@Arg
int id;
@Arg
String title; @Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
FragmentArgs.inject(this); // read @Arg fields
} @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Toast.makeText(getActivity(), "Hello " + title, Toast.LENGTH_SHORT)
.show();
}
}

Simply add comment fields to your Fragment class and FragmentArgs will generate reference code. In your Activity you will use the generated Builder class (your fragment suffix is "Builder") instead of using new MyFragment() or the static ES113en.newInstance (int id,String title) method.

Such as:


public class MyActivity extends Activity
{
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
int id = 123;
String title = "test"; // Using the generated Builder
Fragment fragment = new MyFragmentBuilder(id, title).build(); // Fragment Transaction
getFragmentManager().beginTransaction().replace(R.id.container,fragment).commit();
}
}

You may have noticed FragmentArgs. inject(this) stated in ES125en. onCreate(Bundle). This call gives your fragment a connection to the generated code. You might ask yourself, "Do I need to add the inject() method to onCreate(Bundle) for every Fragment?" The answer is no. You simply insert this sentence in your fragment base class and inherit it in all fragment classes.


public class BaseFragment extends Fragment
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
FragmentArgs.inject(this); // read @Arg fields
}
} public class MyFragment extends BaseFragment
{
@Arg
String title; @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Toast.makeText(getActivity(), "Hello " + title, Toast.LENGTH_SHORT)
.show();
}
}

Credit: Part 1 of the comment generation code is based on Hugo Visser's Bundles project.


Related articles: