2

In my app's activity, I have a ViewPager implementation which loads a Map fragment (FragmentA) on launch and then there are 2 other fragments showing content based on the latitude and longitude selected in FragmentA.

I am unable to get the behavior where upon launch, the latitude/longitude from FragmentA, should be provided to the 2 other fragments and continue to provide whenever user clicks on the map in FragmentA.

Here are the approaches I tried:

  1. using interface to communicate between fragment -> activity -> fragment (sticking with this approach)
  2. using bundle to pass information between fragments when the fragments are getting initialized

However, neither of these approaches have worked for me.

MyActivity.java//update to show Daniel's suggestion

public class MyDemoActivity extends FragmentActivity
                implements FragmentA.OnFragmentInteractionListener,
                    FragmentB.OnFragmentInteractionListener, IUserLatLong  {

    private MyPagerAdapter pagerAdapter;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map_in_pager_demo);

        ViewPager mPager = (ViewPager) findViewById(R.id.pager);
        pagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), getApplicationContext());
        mPager.setAdapter(pagerAdapter);

        //always start w/ Maps View, FragmentA
        mPager.setCurrentItem(1);

        //
        mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
           @Override
           public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            //do something
           }

           @Override
           public void onPageSelected(int position) {
             // do this instead of calling, notifyDataSetChanged()
            Fragment frag = pagerAdapter.fragments[position];
            if (frag != null && frag instanceof FragmentB) {
                Log.i(TAG, "::FragmentB:: Fetching data from Activity");
                //here is my confusion, calling FragmentB with latlong from FragmentA
                ((FragmentB) frag).setNewLatLong(newUserLatLong);
            }
           }

           @Override
           public void onPageScrollStateChanged(int state) {
             //do something
           }
       });
    }

    @Override
    public void onFragmentInteraction(Uri uri) {
        //do something
    }

    @Override
    public void makeUseOfNewLocation(UserLatLong userLatLong) {
       newUserLatLong = userLatLong;
       Log.i(TAG, "LatLong from FragmentA is: " + newUserLatLong.getLat()
                + " and long is: " + newUserLatLong.getLng());
    }

/**
 * Used for fetching data from activity
 * @return
 */
public UserLatLong getLLFromActivity() {
      if(newUserLatLong == null) {
        Log.e(TAG, "LatLong from FragmentA came up empty");
      } else {

        Log.i(TAG, "LatLong from FragmentA IS NOT empty: " + newUserLatLong.getLat()
                + " and long is: " + newUserLatLong.getLng());
      }
      return newUserLatLong;
    }

}

MyPagerAdapter.java

public class MyPagerAdapter extends FragmentPagerAdapter {

    private static int NUM_ITEMS = 3;
    public MyPagerAdapter(FragmentManager fm, Context context) {
       super(fm);
       this.context = context; 
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    // not sure if this is really helping
    @Override
    public int getItemPosition(Object object) {
        // POSITION_NONE makes it possible to reload the PagerAdapter
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                //uses location from FragmentA to get data
                return new FragmentB();
            case 1:
                //loads map and gets location
                return new FragmentA();
            case 2:
                //uses location from FragmentA to get data
                return new FragmentC();
            default:
                return null;
        }
    }

   //This populates your Fragment reference array:
   @Override
   public Object instantiateItem(ViewGroup container, int position) {
       Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
       fragments[position]  = createdFragment;
       Log.i(TAG, "::instantiateItem:: " + position + " " + createdFragment.toString());
       return createdFragment;
    }

}

FragmentA.java /**left out most of map specific code, but I am interested in getting latitude/longitude whenever onConnected() or onMapClick() gets called*/

@Override
public void onConnected(@Nullable Bundle bundle) {
     Log.i(TAG, "In onConnected(), Google API client is:: " + mGoogleApiClient.isConnected());

     if (mGoogleApiClient != null) {
         try {
                // Get last known recent location.
                mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);

                if (mCurrentLocation != null) {
                    // Print current location if not null
                    final LatLng latLng = new LatLng(mCurrentLocation.getLatitude(),
                                                        mCurrentLocation.getLongitude());

                    //wrap LatLng into UserLatLong
                    userLatLong.setLat(latLng.latitude);
                    userLatLong.setLng(latLng.longitude);
//updating the value in the interface
                    mLLCallback.makeUseOfNewLocation(userLatLong);

                    CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, 20);
                    mMap.animateCamera(cameraUpdate);
                    mMap.addMarker(new MarkerOptions().position(latLng));
                    Log.i(TAG, "::Google::My current location set::");

                    mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
                        @Override
                        public void onMapClick(LatLng latLng1) {
                            mMap.clear(); //removes the previous marker
                            mMap.addMarker(new MarkerOptions().position(latLng1));

                //updating the value in the interface
                mLLCallback.makeUseOfNewLocation(userLatLong);
                            float x = (float) latLng1.latitude;
                            float y = (float) latLng1.longitude;
                            Log.d(TAG, "Map clicked w/ lat: " + x + " and long: " + y);

                            //wrap LatLng into UserLatLong
                            userLatLong.setLat(latLng1.latitude);
                            userLatLong.setLng(latLng1.longitude);

                        }
                    });

                mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));

                    //for zooming automatically to the location of the marker
                    CameraPosition cameraPosition = new CameraPosition.Builder().target(latLng).zoom(12).build();
                    mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

                    //TODO: Constrain the camera target to bounds defined by API
                    mMap.setMinZoomPreference(Constants.DEFAULT_MIN_ZOOM);
                    mMap.setMaxZoomPreference(Constants.DEFAULT_MAX_ZOOM);

                } else {
                    Toast.makeText(getActivity(), "Current location is null", Toast.LENGTH_SHORT).show();
                }
            } catch (SecurityException se1) {
                Log.e(TAG, "SecurityException1: " + se1.getLocalizedMessage());
            }

        } else {
            Toast.makeText(getActivity(), "Google API client is null!", Toast.LENGTH_SHORT).show();
        }

FragmentB /**in this fragment, I want to refresh content based on latitude/longitude from FragmentA*/

public class FragmentB extends Fragment {
    /**
     * Required empty public constructor
     */
    public FragmentB() {
    }


/**the getArguments() is always null*/
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    currLatLong = getNewLatLong();
    }

    /**I want latitude/longitude from FragmentA before onCreateView gets called*/ 
    @Override
    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
                             Bundle savedInstanceState) {

        LayoutInflater inflater1 = LayoutInflater.from(getContext());
        // Inflate the layout for this fragment
        view = inflater1.inflate(R.layout.fragment_a, container, false);
       currLatLong = getNewLatLong();
        //fetch my list based on LatLong
        handleDataFetch(currLatLong);

        return view;
    }

    //
    private void handleDataFetch(UserLatLong newLatLong) {
        final UserLatLong latLong = newLatLong;
        final APIEndpointI apiEndpointI = APIRequests.getClient().create(APIEndpointI.class);

        String userId = "XXXX-XXX-XXXXXXX";

        currLatLong = new UserLatLong(newLat.doubleValue(), newLong.doubleValue());
        if (currLatLong != null) {
            Log.i(TAG, "Finally! Lat is: " + currLatLong.getLat() +
                    " and Long is:" + currLatLong.getLng());
            /**using retrofit*/
            Call<MyResp> call = apiEndpointI.getMyList(userId, currLatLong);
            call.enqueue(new Callback<MyResp>() {
                @Override
                public void onResponse(Call<MyResp> call, Response<MyResp> response) {


                    Log.i(TAG, "Count: " + response.body().getBundles().size());
                    Resources res = getContext().getResources();
                    String cntString = String.format(res.getString(R.string.count), response.body().getBundles().size());
                    tv1.setText(cntString);

                    //Initialize with empty data
                    mGridData = new ArrayList<>();
                    mGridAdapter = new ProfilePicAdapter(getActivity(), R.layout.grid_item_layout, mGridData);
                    mGridView.setAdapter(mGridAdapter);

                   }    
                @Override
                public void onFailure(Call<MyResp> call, Throwable t) {
                    //do something here
                    Log.d(TAG, "Failed to get response for GetMyList(): " + t.getMessage());
                }
            });
        } else {
            Log.e(TAG, "In onCreateView(), lat long are empty");
        }
    }


   //called from activity to pass the latest latlong
   public void setNewLatLong(UserLatLong userLatLong) {
      currLatLong = userLatLong;
   }

   //called from activity to pass the latest latlong
   private UserLatLong getNewLatLong() {
      return currLatLong;
   }

}
Anil Gorthy
  • 869
  • 3
  • 13
  • 30
  • In the above implementation, the fragment that I swipe to `FragmentB` doesn't get updated w/ the new location from `FragmentA` instead a different fragment, `FragmentC` gets the updated location. I am not sure why that is happening. I am using approach # 1, using interface to communicate between fragments. – Anil Gorthy Oct 14 '16 at 16:50
  • Made some progress - updated **MyActivity** with `addOnPageChangeListener` but now Map fragment, **FragmentA** also gets refreshed which creates 2 problems: #1 selected map location is only available to the view that I swipe to and, if I swipe again, I return to Map fragment **FragmentA** which gets created again and hence the newly selected fragment doesn't get the previously selected location #2 because of `pagerAdapter.notifyDataSetChanged();` all the fragments get refreshed and the refresh is particularly visually noticeable on the Map fragment. – Anil Gorthy Oct 15 '16 at 16:52
  • 1
    No need to call `notifyDataSetChanged()` on the PagerAdapter. Just keep a reference to the ViewPager Fragments, store the current data in the Activity, and give the latest data to the relevant Fragment each time it loads. See the answer here: http://stackoverflow.com/questions/36503779/refresh-data-in-viewpager-fragment – Daniel Nugent Oct 15 '16 at 19:38
  • @DanielNugent thank you. You are kind enough to post lot of answers and you responded to my question! Based on your response, I am unsure of passing data from activity to `FragmentB`/`FragmentC`. Current impl: defined an interface `IUserLatLong` with only `makeUseOfNewLocation(userLatLong)`. `FragmentA` implements, gets location data and passes to this method, and the Activity too implements this interface so, in `onCreateView` of `FragmentB`/`FragmentC` they just call a public method `getLLFromActivity()` in Activity to get LatLong. How do I go about data transfer in this new approach? – Anil Gorthy Oct 16 '16 at 17:05
  • The reason is that neither `FragmentB.newInstance(userLatLong)` or calling `bundle` have worked for me . – Anil Gorthy Oct 16 '16 at 17:12
  • 1
    It looks good for the most part. Looks like you should also call ``((FragmentB) frag).handleDataFetch(newUserLatLong);`` after the call to `((FragmentB) frag).setNewLatLong(newUserLatLong);`, but only if the location has changed. – Daniel Nugent Oct 16 '16 at 17:27
  • That is not helping as I don't see `FragmentB`'s `onCreateView()` being called because `handleDataFetch(newUserLatLong)` is called from `onCreateView()`. – Anil Gorthy Oct 16 '16 at 18:02
  • I stand correted. `FragmentB`/`FragmentC` are getting the updated location and hence updated content. – Anil Gorthy Oct 16 '16 at 18:41

1 Answers1

2

It looks like all you need to complete the implementation is to call handleDataFetch() from the setNewLatLong() method if it is a new location.

This is needed since onCreateView() won't be called due to the Fragment being in a ViewPager, so the best way to get the updated location info to FragmentB when it is displayed is to just use a public method called from the ViewPager.OnPageChangeListener in the Activity, as you are doing here.

Just be sure that your equals() method override is implemented correctly in the UserLatLong class, and add this to the setNewLatLong() method:

   //called from activity to pass the latest latlong
   public void setNewLatLong(UserLatLong userLatLong) {
      //Added:
      if (!currLatLong.equals(userLatLong)) {
          handleDataFetch(userLatLong);
      }
      currLatLong = userLatLong;
   }
Community
  • 1
  • 1
Daniel Nugent
  • 43,104
  • 15
  • 109
  • 137