Jetpack Navigation Fragmen data transfer
Navigation is a framework for navigating between “target functions” in Android apps, and the framework provides a consistent API, whether the target functions are implemented as fragments, activities, or other components.
Several scenarios where data needs to be passed
- Page migration requires data to be passed from AFragment to BFragment.
- Page rollback requires data to be passed from BFragment to AFragment.
How data is transferred during page migration
Passing data during a migration from AFragment to BFragment in development is a very common business scenario.
- Pass data directly through Bundl:
private void goBFragment(a) {
Bundle args = new Bundle();
args.putString("userName"."Zhang");
Navigation.findNavController(getView())
.navigate(R.id.action_AFragment_to_BFragment, args);
}
Copy the code
public final class BFragment extends Fragment{
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String ueserName = getArguments().getString("userName"); }}Copy the code
The above code can achieve data transfer and acquisition, but this method requires both parties to agree on the data Key and data type. A change by one party can easily lead to unknown problems.
- Official recommended safeArgs data transfer method:
- Add plug-ins under the project root build.gradle
Dependencies {classpath "androidx. Navigation: navigation - safe - the args - gradle - plugin: 2.3.2"}Copy the code
- You then rely on plug-ins in your Module’s build.gradle
apply plugin: "androidx.navigation.safeargs"
Copy the code
- Add the data to navigation. XML to declare the data to be passed
<fragment
android:id="@+id/BFragment"
android:name="org.alee.demo.navigation.back.BFragment"
android:label="BFragment">
<argument
android:name="userName"
app:argType="string"
android:defaultValue="Zhang"
/>
</fragment>
Copy the code
- SafeArgs will automatically generate some code after you add the rebuild project
SafeArgs will generate classes based on the fragment tag in nav_graph. The action tag will be named “Class name +Directions”, and the argument tag will be named “class name +Args”.
- Migrate pages and transfer data
private void goBFragment(a) {
Navigation.findNavController(getView())
.navigate(AFragmentDirections.actionAFragmentToBFragment().setUserName("Zhang"));
}
Copy the code
- Receive data
public final class BFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); String userName = BFragmentArgs.fromBundle(getArguments()).getUserName(); }}Copy the code
Undoubtedly, when using safeArgs to Fragment data transmission, the plug-in manages the Key and type of data for us, making it more secure and convenient.
Data transfer mode during page rollback
In the navigation service, a point should be picked up from the route result page (AFragment) to the map pickup page (BFragment). The BFragment needs to pass the picked up points of interest to AFragment. However, using the NavController.navigate()API would result in the creation of a new AFragment with onCreate(), onCreateView(), onViewCreated() executed, which is not desirable. We want the BFragment to simply pass the result of processing to AFragment.
Is there anything like startActivityForResult(Intent Intent, int requestCode, @nullable Bundle options), setResult(int resultCode, Intent data). The answer is no, Navigation currently does not support sending data back to popBackStack. But we can implement a similar interface based on Navigation.
The solution
public abstract class BaseFragment extends Fragment {
protected ResultArgs mArgs;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(requireLayoutId(), container, false);
}
protected abstract @LayoutRes
int requireLayoutId(a);
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mArgs = new ResultArgs(getArguments());
onFindView(view);
onBindListener();
}
protected abstract void onFindView(View rootView);
protected abstract void onBindListener(a);
protected void go(@IdRes int destination) {
go(destination, null);
}
protected void go(@IdRes int destination, Bundle bundle) {
getNavController().navigate(destination, bundle);
}
protected void back(a) {
getNavController().popBackStack();
}
protected <T> void setResult(T data) {
if (null == mArgs || 0 >= mArgs.getRecipientId()) {
return; } getNavController().getBackStackEntry(mArgs.getRecipientId()).getSavedStateHandle().getLiveData(String.valueOf(mArgs.getR equestCode())).postValue(new Pair(mArgs.getRequestCode(), data));
}
/ * * *@paramDestination is the page to migrate to@paramThe requestCode is the same as the StartActivityForResult requestCode *@param<T> Returns data type *@return {@link LiveData} Pair.first: requestCode; Pair.second: resultData
*/
protected <T> LiveData<Pair<Integer, T>> startFragmentForResult(@IdRes int destination, int requestCode) {
return startFragmentForResult(destination, requestCode, null);
}
protected <T> LiveData<Pair<Integer, T>> startFragmentForResult(int requestCode, @NonNull NavDirections destination) {
return startFragmentForResult(destination.getActionId(), requestCode, destination.getArguments());
}
protected <T> LiveData<Pair<Integer, T>> startFragmentForResult(@IdRes int destination, int requestCode, @Nullable Bundle bundle) {
ResultArgs args = new ResultArgs(getNavController().getCurrentDestination().getId(), requestCode).setBusinessArgs(bundle);
LiveData<Pair<Integer, T>> liveData = getNavController().getCurrentBackStackEntry().getSavedStateHandle().getLiveData(String.valueOf(requestCode));
getNavController().navigate(destination, args.toBundle());
return liveData;
}
protected void popTo(@IdRes int destination) {
getNavController().popBackStack(destination, true);
}
protected NavController getNavController(a) {
returnNavigation.findNavController(getView()); }}Copy the code
StartFragmentForResult relies primarily on the SavedStateHandle object provided by Navigation to hold a LiveData, and the three overloaded functions perfectly support all jumps. Return a life-cycle aware LiveData to receive the returned data. This makes the Fragment that initiated the jump not receive the returned data in the inactive state. After the Fragment is destroyed, the binding relationship between the Fragment and the observer is automatically released to prevent memory leakage and excessive memory consumption.
SetResult finds the SavedStateHandle corresponding to the Fragment to receive data and sends it out for the subscriber to receive by traversing the rollback stack managed internally by NavController.
public class ResultArgs {
private static final String RECIPIENT_ID = "resultArgsRecipientId";
private static final String REQUEST_CODE = "ResultArgsRequestCode";
private static final String BUNDLE = "ResultArgsBundle";
private final Map<String, Object> mArgsMap = new HashMap<>();
public ResultArgs(@IdRes int recipientId, int requestCode) {
mArgsMap.put(RECIPIENT_ID, recipientId);
mArgsMap.put(REQUEST_CODE, requestCode);
}
public ResultArgs(Bundle bundle) {
if (null == bundle) {
return;
}
setBusinessArgs(bundle);
mArgsMap.put(RECIPIENT_ID, bundle.getInt(RECIPIENT_ID));
mArgsMap.put(REQUEST_CODE, bundle.getInt(REQUEST_CODE));
}
public Bundle toBundle(a) {
Bundle temp = new Bundle();
if (null! = getBusinessArgs()) { temp.putAll(getBusinessArgs()); } temp.putInt(RECIPIENT_ID, getRecipientId()); temp.putInt(REQUEST_CODE, getRequestCode());return temp;
}
public @IdRes
int getRecipientId(a) {
return (int) mArgsMap.get(RECIPIENT_ID);
}
public int getRequestCode(a) {
return (int) mArgsMap.get(REQUEST_CODE);
}
public ResultArgs setBusinessArgs(Bundle businessArgs) {
if (null == businessArgs) {
return this;
}
mArgsMap.put(BUNDLE, businessArgs);
return this;
}
public Bundle getBusinessArgs(a) {
return(Bundle) mArgsMap.get(BUNDLE); }}Copy the code
ResultArgs is similar to the Bundle parameter management class that safeArgs generated above
use
public final class AFragment extends BaseFragment {
private final Observer mViaPoiResultObserver = new Observer<Pair<Integer, Object>>() {
@Override
public void onChanged(Pair<Integer, Object> integerObjectPair) {
Log.i(AFragment.class.getSimpleName(), "RequestCode: [ " + integerObjectPair.first + "]" + " ResultData: [ " + integerObjectPair.second + "]"); }};private void goBFragment(a) {
startFragmentForResult(R.id.action_AFragment_to_BFragment,2)
.observe(this, mViaPoiResultObserver); }}Copy the code
public final class BFragment extends BaseFragment {
@Override
protected void onBindListener(a) {
mBackBtn.setOnClickListener(view -> {
setResult("I'm returning data."); back(); }); }}Copy the code
Suppose there is A business requirement a-B-C, and C is closed to return to A and pass data to A:
public final class AFragment extends BaseFragment {
private final Observer mViaPoiResultObserver = new Observer<Pair<Integer, Object>>() {
@Override
public void onChanged(Pair<Integer, Object> integerObjectPair) {
Log.i(AFragment.class.getSimpleName(), "RequestCode: [ " + integerObjectPair.first + "]" + " ResultData: [ " + integerObjectPair.second + "]"); }};private void goBFragment(a) {
startFragmentForResult(R.id.action_AFragment_to_BFragment,2).observe(this, mViaPoiResultObserver); }}Copy the code
public final class BFragment extends BaseFragment {
private void goCFragment(a){ go(R.id.action_BFragment_to_CFragment,mArgs.toBundle()); }}Copy the code
public final class CFragment extends BaseFragment {
@Override
protected void onBindListener(a) {
mCloseBtn.setOnClickListener(view -> {
setResult("I'm returning data."); popTo(R.id.BFragment); }); }}Copy the code
It’s also very simple to use!