Reputation: 83557
In my Android app, I have the following navgraph:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/card_list">
<fragment
android:id="@+id/card_list"
android:name="bbct.android.common.fragment.BaseballCardList"
android:label="@string/app_name"
tools:layout="@layout/card_list">
<action
android:id="@+id/action_details"
app:destination="@id/card_details" />
</fragment>
<fragment
android:id="@+id/card_details"
android:name="bbct.android.common.fragment.BaseballCardDetails"
android:label="@string/card_details_title"
tools:layout="@layout/card_details">
<argument
android:name="id"
app:argType="long" />
</fragment>
</navigation>
As you can see card_details
takes an id
argument. If this is -1
, then the detail view is opened with all blank fields and allows the user to create a new card. If id
is a positive value, it is used to fetch a row from the database and populates the form for editing.
Now I am writing a test to verify that while editing a card that the app navigates back to card_list
when the user clicks the save button:
public class NavigationTest {
@Test
public void clickOnSaveNavigatesFromDetailsBackToList() {
TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext());
FragmentScenario<BaseballCardDetails> detailsScenario = FragmentScenario.launchInContainer(
BaseballCardDetails.class,
null,
R.style.AppTheme
);
detailsScenario.onFragment(fragment -> {
navController.setGraph(R.navigation.nav_graph);
BaseballCardDetailsArgs args = new BaseballCardDetailsArgs.Builder(0).build();
navController.setCurrentDestination(R.id.card_details, args.toBundle());
Navigation.setViewNavController(fragment.requireView(), navController);
});
assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId())
.isEqualTo(R.id.card_details);
Espresso.onView(ViewMatchers.withId(R.id.save_button)).perform(ViewActions.click());
assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId())
.isEqualTo(R.id.card_list);
}
}
However, when this test navigates to card_details
, the form fields are not populated by the card's values. To see what is going on, I added Log.d()
calls to my app:
public class BaseballCardDetails extends Fragment {
// ...
@Override
public View onCreateView(
@NonNull LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
// ...
populateTextEdits();
}
private void populateTextEdits() {
Bundle args = getArguments();
Log.d(TAG, "args: " + args);
// ...
}
}
This outputs
05-31 21:35:13.589 29002 29002 D bbct.android.common.fragment.BaseballCardDetails: args: null
As you can see args
is null
. Why isn't the arguments from my test being passed through to the fragment here? More importantly, how can I fix it?
Upvotes: 0
Views: 558
Reputation: 200020
When you write:
FragmentScenario<BaseballCardDetails> detailsScenario = FragmentScenario.launchInContainer(
BaseballCardDetails.class,
null,
R.style.AppTheme
);
That null
are the arguments passed to your fragment. Hence, getArguments()
is null because the arguments you've passed to your fragment is null. Since the NavController isn't creating your fragment in your test (instead, it is launchInContainer
that is creating it), the arguments you set via setCurrentDestination
only apply to the NavBackEntry
's arguments (i.e., if you call navController.getCurrentBackStackEntry().getArguments()
).
Instead of passing null
to launchInContainer
, you'll want to move the construction of your args
Bundle to before your call to launchInContainer
:
@Test
public void clickOnSaveNavigatesFromDetailsBackToList() {
TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext());
// Move args construction here
BaseballCardDetailsArgs args = new BaseballCardDetailsArgs.Builder(0).build();
Bundle bundleArgs = args.toBundle();
// And pass the bundleArgs into launchInContainer:
FragmentScenario<BaseballCardDetails> detailsScenario = FragmentScenario.launchInContainer(
BaseballCardDetails.class,
bundleArgs,
R.style.AppTheme
);
detailsScenario.onFragment(fragment -> {
navController.setGraph(R.navigation.nav_graph);
// Reuse the same bundleArgs here as well
navController.setCurrentDestination(R.id.card_details, bundleArgs);
Navigation.setViewNavController(fragment.requireView(), navController);
});
assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId())
.isEqualTo(R.id.card_details);
Espresso.onView(ViewMatchers.withId(R.id.save_button)).perform(ViewActions.click());
assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId())
.isEqualTo(R.id.card_list);
}
Upvotes: 1