Search code examples
javaandroidandroid-fragmentsandroid-recyclerviewbundle

Passing value from Adapter to a BottomNavigationView Fragment in Navigation components


The application has a BottomNavigationView on the MainActivity which deals with the navigation between Fragments. One of the Fragments has a RecyclerView in it and the items of the RecyclerView have a button.

I am trying to make the RecyclerView Items' button navigate to another Fragment and pass a value along with it but I keep receiving a null object reference with passing the data.

  1. I created an interface to get the click to the MainActivity for the Fragment switch and created a Bundle to communicate the received value to the required Fragment
// The interface
public interface OnItemClickListener {

    void onItemClick();

The Adapter class

//Define interface
 OnItemClickListener listener;

//Create constructor for interface
    public LocationsAdapter(Activity activity) {
        listener = (MainActivity)activity;

...
 @Override
    public void onBindViewHolder(@NonNull LocationsViewHolder holder, int position) {

//A click listener for the button
        holder.showMarkerButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//Create instance of MapFragment to pass data as Bundle
                MapFragment mapFragment = new MapFragment();
 LatLng latLng = new LatLng(markerObject.getLatitude(),markerObject.getLongitude());
//Create Bundle and add data
                Bundle bundle = new Bundle();
bundle.putString("latitude",markerObject.getLatitude().toString());
bundle.putString("longitude",markerObject.getLongitude().toString());
                listener.onItemClick(bundle);
                    }
        });
}
  1. Then in the MainActivity I implemented the interface to switch the View of the BottomNavigation.
public class MainActivity extends AppCompatActivity implements OnItemClickListener {
    BottomNavigationView navView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        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_map, R.id.navigation_locations, R.id.navigation_profile)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        NavigationUI.setupWithNavController(navView, navController);
    }

    @Override
    public void onItemClick() {
        navView.setSelectedItemId(R.id.navigation_map);
    }
}
  1. As a final step I called on the Bundle I made in the first step but that's where the null object reference comes up.
//MapFragment
private void setCam(){
        Bundle bundle = getArguments();
        if (getArguments() != null ){
            String SLatitude = bundle.getString("latitude");
            String SLongitude = bundle.getString("longitude");
            Double latitude = Double.parseDouble(SLatitude);
            Double longitude = Double.parseDouble(SLongitude);
            LatLng latLng = new LatLng(latitude,longitude);
            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 20));
            Toast.makeText(getActivity(), latLng.toString(), Toast.LENGTH_SHORT).show();
        }
        else{
            Toast.makeText(getActivity(), "error present", Toast.LENGTH_SHORT).show();
        }
    }

Thanks in advance.


Solution

  • MapFragment mapFragment = new MapFragment();
    LatLng latLng = new LatLng(markerObject.getLatitude(),markerObject.getLongitude());
    //Create Bundle and add data
    Bundle bundle = new Bundle();
    bundle.putString("position",latLng.toString());
    mapFragment.setArguments(bundle);
    

    Here the mapFragment is just a created object that is not used in the navigation graph. i.e. it's a standalone object that is independent of the navigation, so it is not involved in the navigation as it's not the same as the current mapFragment fragment in the navGraph.

    Solution:

    In order to solve this, you need to pass the argument whenever you switch among fragments of the BottomNavigationView, and registering OnNavigationItemSelectedListener is a good place for that:

    First allow the adapter to send the arguments back to the activity through the listener callback

    So, modify the listener callback to accept a parameter

    interface OnItemClickListener listener {
        void onItemClick(Bundle args);
    }
    

    Apply that on adapter

    @Override
        public void onBindViewHolder(@NonNull LocationsViewHolder holder, int position) {
    
            //A click listener for the button
            holder.showMarkerButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    
                    LatLng latLng = new LatLng(markerObject.getLatitude(),markerObject.getLongitude());
                    
                    //Create Bundle and add data
                    Bundle bundle = new Bundle();
                    bundle.putString("position",latLng.toString());
                    
                    //Interface to transport the click event to the MainActivity to switch Fragment in the BottomNavigationView
                    listener.onItemClick(bundle);
                }
            });
    }
    

    And finally, register OnNavigationItemSelectedListener to the navView and modify the callback to accept the Bundle in activity:

    public class MainActivity extends AppCompatActivity implements OnItemClickListener {
        BottomNavigationView navView;
        
        private Bundle mapArgs;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            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_map, R.id.navigation_locations, R.id.navigation_profile)
                    .build();
            NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
            NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
            NavigationUI.setupWithNavController(navView, navController);
            
            
            navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                    navController.navigate(item.getItemId(), mapArgs);
                    return true;
                }
            });     
            
        }
    
        @Override
        public void onItemClick(Bundle args) {
            navView.setSelectedItemId(R.id.navigation_map);
            mapArgs = args;
        }
    
    
    }