0

I've created an application using the Bottom Navigation Activity template in Android Studio. In the main activity's onCreate() method I added an OnDestinationChangedListener to the NavController. I want to call some methods in the selected Fragment after it changes. How can I get the selected Fragment from the onDestinationChanged() in the listener? The onCreate() code is as follows:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BottomNavigationView navView = findViewById(R.id.nav_view);
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        NavigationUI.setupWithNavController(navView, navController);

        navController.addOnDestinationChangedListener(
                new NavController.OnDestinationChangedListener() {
                    @Override
                    public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
                        switch (destination.getId()) {
                            case R.id.navigation_home: {
                                Log.i(TAG, "navigation_home");
                                // Here is where I want to get the HomeFragment instance.
                                break;
                            }
                        }
                    }
                }
        );
    }

EDIT: Please notice I'm not creating the fragments by myself, see the three lines before the call to navController.addOnDestinationChangedListener().

EDIT: This is the Xml in activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

For bottom_nav_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home" />

    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard_black_24dp"
        android:title="@string/title_dashboard" />

    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications_black_24dp"
        android:title="@string/title_notifications" />

</menu>

For fragment_home.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.home.HomeFragment">

    <TextView
        android:id="@+id/text_home"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:textAlignment="center"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

fragment_dashboard.xml and fragment_notifications.xml are very similar to fragment_home.xml.

This is the code for HomeFragment

public class HomeFragment extends Fragment {
    private final static String TAG = BleService.class.getSimpleName();

    private HomeViewModel homeViewModel;

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
        View root = inflater.inflate(R.layout.fragment_home, container, false);

        final TextView textView = root.findViewById(R.id.text_home);
        homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });

        return root;
    }
}

and for HomeViewModel

public class HomeViewModel extends ViewModel {

    private MutableLiveData<String> mText;

    public HomeViewModel() {
        mText = new MutableLiveData<>();
        mText.setValue("This is home fragment");
    }

    public LiveData<String> getText() {
        return mText;
    }
}

With the exception of tests I've been doing regarding the creation of one Service and its use from MainActivity, the rest of the project is pretty much the code generated by AndroidStudio when I created the project.

V.Lorz
  • 293
  • 2
  • 8
  • Does this answer your question? [Android Navigation Architecture Component - Get current visible fragment](https://stackoverflow.com/questions/51385067/android-navigation-architecture-component-get-current-visible-fragment) – hata Feb 10 '21 at 23:34
  • hata, thanks. I went through that post before posting my question and tried what they were suggesting there without success. In my case, the fragment is not being created in my code in the main activity. Check the three lines before the call to navController.addOnDestinationChangedListener(). – V.Lorz Feb 11 '21 at 09:07

2 Answers2

0

Actually you need to pass setNavigationItemSelectedListener on your navView like this navView.setNavigationItemSelectedListener(this); then add this code in your onNavigationItemSelected case R.id.navigation_home: Navigation.findNavController(this,R.id.nav_host_fragment).navigate(R.id.profile); Note:Replace R.id.profile with your navigation graph.The code is just a sample for what you want to achieve. Dont forget to implement NavigationView.onNavigationItemSelectedListener in your main Activity

Danish
  • 676
  • 5
  • 10
  • Danish, thanks. My intention is not creating the fragments by myself. See the three lines before the call to navController.addOnDestinationChangedListener(). – V.Lorz Feb 11 '21 at 09:09
  • Got your point dude!Check the updated answer – Danish Feb 11 '21 at 12:30
  • Danish, thanks again. Using the suggested call I can play jokes with the user, like displaying the selected icon of one fragment but displaying the content of another different fragment. But it does not allow me to get the selected fragment's instance. – V.Lorz Feb 11 '21 at 12:58
  • If you could tell more about your problem then it would be easy to understand. – Danish Feb 11 '21 at 13:41
  • Check this link its a sample of what you need to do. https://github.com/mitchtabian/Dagger-Examples/blob/navcontroller-and-navigation-graph/app/src/main/java/com/codingwithmitch/daggerpractice/ui/main/MainActivity.java – Danish Feb 11 '21 at 14:53
  • This is video form of above method: https://youtu.be/wwStpiU4nJk – Danish Feb 11 '21 at 14:55
  • Don't see the point in calling Navigation.findNavController(this,R.id.nav_host_fragment).navigate(R.id.profile); and interfere with what NavigationUI is already doing. Thanks a lot for your time and comments. – V.Lorz Feb 11 '21 at 18:25
  • Its ok.Just curious to know if you found the solution or not.. – Danish Feb 11 '21 at 18:32
0

Apparently, it is not possible to obtain the selected fragment inside the call to onDestinationChanged(). This listener's method gets called before the fragment is created. Several answers in another similar question suggest using something like (note null checks are removed, for simplicity):

Fragment navHostFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);

But the documentation for NavController.OnDestinationChangedListener states

This method is called after navigation is complete, but associated transitions may still be playing.

So, while playing with the above code, first time onDestinationChanged() gets called the fragments list returned by navHostFragment.getChildFragmentManager().getFragments() is empty, which is consistent with previous excerpt from the documentation. But for the second time it is called by the navController the list does contain 1 element, in my case, the one that was selected before the change.

There should be more ways to accomplish this, but the way I found for passing data to the fragment is this one:

  1. Create one helper Binder to be passed as argument to the Fragment:

     public interface IMyAppServices {
         String getSomeText();
     }
    
     public class MyAppServicesBinder extends Binder {
         public IMyAppServices getService() {
             return new IMyAppServices() {
                 @Override
                 public String getSomeText() {
                     return "This is some text";
                 }
             };
         }
     }
    
     private final MyAppServicesBinder _myAppServicesBinder = new MyAppServicesBinder();
    
  2. In the NavController's initialisation add following code, note this is just for testing purposes, see example in the documentation here:

     NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
     Bundle args = new Bundle();
     args.putString("My string", "Is this");
     args.putBinder("MyAppServices", _myAppServicesBinder);
     navController.setGraph(R.navigation.mobile_navigation, args);
    
  3. Now in the Fragment those arguments can be read by calling getArguments() like this:

     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Bundle arguments = getArguments();
         MainActivity.MyAppServicesBinder binder = (MainActivity.MyAppServicesBinder)arguments.getBinder("MyAppServices");
         String someText = binder.getService().getSomeText();
         Log.i(TAG, "onCreate() called with SomeTest = " + someText); 
     }
    

This provides me the means for accessing some application services or data providers from the Fragment. Now the thing is how to do the opposite, in a simple way, and triggered by the event that the selected Fragment has changed.

V.Lorz
  • 293
  • 2
  • 8