1

I have a MainActivity which first makes a call to my server and gets the response. Then, on the success of first response, i am initiating the viewPager.

Below, code is called to initiate the viewPager and it's adapter, it is called only after the success of first response.

mShowTimeViewPageAdapter = new ShowTimeViewPageAdapter(getSupportFragmentManager(), getApplicationContext(), this, mDateStringList, showDateCodesList, venueHashMap);

mVenueHashMap = venueHashMap;

mRequestedDateCodes = requestedDateCodes;

mViewPager.setAdapter(mShowTimeViewPageAdapter);
mShowTimeTabLayout.setupWithViewPager(mViewPager);

for (int i = 0; i < range; i++) {
    TabLayout.Tab tab = mShowTimeTabLayout.getTabAt(i);
    tab.setCustomView(mShowTimeViewPageAdapter.getTabView(i));
}

This is my viewPagerAdapter.

public class ShowTimeViewPageAdapter extends FragmentStatePagerAdapter {

    private static final String TAG = ShowTimeViewPageAdapter.class.getSimpleName();


    private Map<Integer, String> mFragmentTags;

    private Context mContext;
    private List<String> mDateStringList;
    private FragmentManager fragmentManager;
    private IShowTimeActivity mIShowTimeActivity;

    private List<String> mDateCodes;
    private LinkedHashMap<String, List<Venue>> mVenueHashMap;

    public ShowTimeViewPageAdapter(FragmentManager fm, Context context, IShowTimeActivity showTimeActivity, List<String> createdDates, List<String> dateCodes, LinkedHashMap<String, List<Venue>> venueHashMap) {
        super(fm);
        this.mContext = context;
        this.mIShowTimeActivity = showTimeActivity;
        this.fragmentManager = fm;
        this.mDateCodes = dateCodes;
        this.mVenueHashMap = venueHashMap;
        this.mDateStringList = createdDates;

        mFragmentTags = new HashMap<Integer, String>();
    }

    private List<Venue> getVenuesList(String dateCode) {
        return mVenueHashMap.get(dateCode);
    }

    @Override
    public Fragment getItem(int position) {
        String stringDate = mDateStringList.get(position).split(";")[2];
        if (mDateCodes.contains(stringDate)) {
            if (getVenuesList(stringDate) == null) {
                mIShowTimeActivity.requestForTheVenueListByDateCode(stringDate, position);
            }
        }

        ShowTimeFragment showTimeFragment = ShowTimeFragment.newInstance(stringDate);
        showTimeFragment.setVenueList(mVenueHashMap.get(stringDate));
//        fragmentManager.beginTransaction().add(showTimeFragment, ""+position);
        return showTimeFragment;
    }

    @Override
    public int getCount() {
        return mDateStringList.size();
    }

    public View getTabView(int position) {
        String[] dateStr = mDateStringList.get(position).split(";");
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        View tabView = layoutInflater.inflate(R.layout.show_time_tab_layout, null, false);
        TextViewRoboto day = (TextViewRoboto) tabView.findViewById(R.id.show_time_tab_day);
        TextViewRoboto date = (TextViewRoboto) tabView.findViewById(R.id.show_time_tab_date);
        day.setText(dateStr[0]);
        date.setText(dateStr[1]);
        return tabView;
    }

}

Here, on getItem() method callback of viewPager, it creates a new fragment for each position and checks if the data for the selected position is available or not. If yes, which will be obviously there for the first position, since it was called after success of first response, it will pass the data to the fragment.

If not, it will make a new api request to the server, to get the data. Since viewPager caches 2 pages, in advance, it will make 2 requests and cache the data.

In each request, i am updating the hashMap with data.

This is my fragment, which is always initiated for each position.

public class ShowTimeFragment extends Fragment {

    private static final String KEY_CODE = "date_key";
    public ShowTimeRecyclerViewAdapter mShowTimeRecyclerViewAdapter;

    @Bind(R.id.show_time_fragment_recycler_view)        RecyclerView mShowTimeRecyclerView;
    @Bind(R.id.show_time_fragment_no_data_text_view)    TextViewRoboto mShowTimeNoDataTextView;

    List<Venue> venueList = new ArrayList<Venue>();

    public List<Venue> getVenueList() {
        return venueList;
    }

    public void setVenueList(List<Venue> venueList) {
        this.venueList = venueList;
    }


    private View view;

    public ShowTimeFragment() {
    }

    public static ShowTimeFragment newInstance(String dateCode) {
        Bundle bundle = new Bundle();
        bundle.putString(KEY_CODE, dateCode);
        ShowTimeFragment showTimeFragment = new ShowTimeFragment();
        showTimeFragment.setArguments(bundle);
        return showTimeFragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.show_time_fragment, container, false);
        ButterKnife.bind(this, view);

        mShowTimeRecyclerViewAdapter = new ShowTimeRecyclerViewAdapter(venueList, getContext());
        mShowTimeRecyclerView.setAdapter(mShowTimeRecyclerViewAdapter);

        if (venueList != null && venueList.size() > 0) {
            mShowTimeRecyclerView.setVisibility(View.VISIBLE);
            mShowTimeNoDataTextView.setVisibility(View.GONE);

            CustomLinearLayoutManager cl = new CustomLinearLayoutManager(getActivity(), 1, false);
            mShowTimeRecyclerView.setLayoutManager(cl);

        }
        else{
            mShowTimeNoDataTextView.setVisibility(View.VISIBLE);
            mShowTimeRecyclerView.setVisibility(View.GONE);
            mShowTimeNoDataTextView.setText("NO Data Available");
        }
        return view;
    }
}

And Below is my recyclerViewAdapter, which is called from the fragment.

public class ShowTimeRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int TYPE_RECOMMENDED = 0;
    private static final int TYPE_GENERIC = 1;
    public List<Venue> mVenueList;
    private Context mContext;

    public ShowTimeRecyclerViewAdapter(List<Venue> venueList, Context context) {
        this.mVenueList = venueList;
        this.mContext = context;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_RECOMMENDED) {
            LayoutInflater layoutInflater = LayoutInflater.from(mContext);
            View view = layoutInflater.inflate(R.layout.show_time_recycler_view_header_item_view, parent, false);
            final RecommendedViewHolder recyclerViewHolder = new RecommendedViewHolder(view);
            return recyclerViewHolder;
        }
        else if (viewType == TYPE_GENERIC) {
            LayoutInflater layoutInflater = LayoutInflater.from(mContext);
            View view = layoutInflater.inflate(R.layout.show_time_recycler_view_item, parent, false);
            final ChildViewHolder viewHolder = new ChildViewHolder(view);
            return viewHolder;
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        Venue venue = mVenueList.get(position);

        if (holder instanceof RecommendedViewHolder) {
            TextViewRoboto cinemaHallName = ((RecommendedViewHolder) holder).mRecommendedShowTimeCinemaHallName;
            cinemaHallName.setText(venue.getVenueName());
            CustomGridView movieTimingGridView = ((RecommendedViewHolder) holder).mRecommendedMovieTimingGridView;

            ImageView reserveIcon = ((RecommendedViewHolder) holder).mRecommendedMovieReserveImage;
            if (venue.getCinemaUnpaidFlag().equals("Y")) {
                reserveIcon.setVisibility(View.VISIBLE);
            }
            else {
                reserveIcon.setVisibility(View.GONE);
            }

            ImageView mTicketIcon = ((RecommendedViewHolder) holder).mRecommendedMovieMTicketImage;
            if (venue.getMTicket().equals("Y")) {
                mTicketIcon.setVisibility(View.VISIBLE);
            }
            else {
                mTicketIcon.setVisibility(View.GONE);
            }

            List<ShowTime> currShowTimesList = new ArrayList<ShowTime>(venue.getShowTimes());
            movieTimingGridView.setAdapter(new ShowMovieTimeAdapter(mContext, currShowTimesList, true));
            movieTimingGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                }
            });
        }
        else if (holder instanceof ChildViewHolder) {
            ((ChildViewHolder) holder).mShowTimeCinemaHallName.setText(venue.getVenueName());

            if (venue.getMTicket().toUpperCase().equals("Y") || venue.getCinemaUnpaidFlag().toUpperCase().equals("Y")) {
                if (venue.getCinemaUnpaidFlag().toUpperCase().equals("N")) {
                    ((ChildViewHolder) holder).mMovieReserveImage.setVisibility(View.GONE);
                }
                else if (venue.getCinemaUnpaidFlag().toUpperCase().equals("Y")) {
                    ((ChildViewHolder) holder).mMovieReserveImage.setVisibility(View.VISIBLE);
                    ((ChildViewHolder) holder).mMovieReserveImage.setImageResource(R.drawable.show_time_reserve_seat_icon);
                }

                if (venue.getMTicket().toUpperCase().equals("N")) {
                    ((ChildViewHolder) holder).mMovieMTicketImage.setVisibility(View.GONE);
                }
                else if (venue.getMTicket().toUpperCase().equals("Y")) {
                    ((ChildViewHolder) holder).mMovieMTicketImage.setVisibility(View.VISIBLE);
                    ((ChildViewHolder) holder).mMovieMTicketImage.setImageResource(R.drawable.show_time_m_ticket_icon);
                }

            }
            else {
                ((ChildViewHolder) holder).mShowTimeFragImagesLinearLayout.setVisibility(View.GONE);
            }

            CustomGridView movieTimingGridView = ((ChildViewHolder) holder).mMovieTimingGridView;

            List<ShowTime> currShowTimesList = new ArrayList<ShowTime>(venue.getShowTimes());


            movieTimingGridView.setAdapter(new ShowMovieTimeAdapter(mContext, currShowTimesList, false));
            movieTimingGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                }
            });
        }
    }

    public static class ChildViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.show_time_fragment_cinema_hall_name)
        TextViewRoboto mShowTimeCinemaHallName;

        @Bind(R.id.show_movie_timings_grid_view)
        CustomGridView mMovieTimingGridView;

        @Bind(R.id.show_time_fragment_reserve_image)
        ImageView mMovieReserveImage;

        @Bind(R.id.show_time_fragment_m_ticket_image)
        ImageView mMovieMTicketImage;

        @Bind(R.id.show_time_fragment_images_lin_layout)
        LinearLayout mShowTimeFragImagesLinearLayout;

        public ChildViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

    public void swap(List<Venue> venueList){
        if(mVenueList!=null){
            mVenueList.clear();
            mVenueList.addAll(venueList);
        } else {
            mVenueList = venueList;
        }
    }

    class RecommendedViewHolder extends RecyclerView.ViewHolder {

        @Bind(R.id.show_time_recommendation_fragment_cinema_hall_name)
        TextViewRoboto mRecommendedShowTimeCinemaHallName;

        @Bind(R.id.show_movie_recommendation_timings_grid_view)
        CustomGridView mRecommendedMovieTimingGridView;

        @Bind(R.id.show_time_recommendation_fragment_reserve_image)
        ImageView mRecommendedMovieReserveImage;

        @Bind(R.id.show_time_recommendation_fragment_m_ticket_image)
        ImageView mRecommendedMovieMTicketImage;

        @Bind(R.id.show_time_recommendation_for_badge)
        ImageView mShowTimeRecommendationBadge;

        public RecommendedViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

    @Override
    public int getItemCount() {
        return mVenueList.size();
    }

    @Override
    public int getItemViewType(int position) {
        if (isPositionRecommended(position))
            return TYPE_RECOMMENDED;

        return TYPE_GENERIC;
    }

    private boolean isPositionRecommended(int position) {
        Venue venue = mVenueList.get(position);
        return venue.isRecommended();
    }
}

My problems lies here, even though the request is asynchronous, i want to show a loader, if the data hasn't come and the update the viewPager fragment on the data has come from server and hideLoading.

It's working fine when i am overriding getItemPosition() of viewPager and returning POSITION_NONE from there, but i don't want to do this, because it's recreating the fragment, and making my app slow.

I tried setting the TAG for each fragment and onPositionSelected() callback of viewPager listener, i tried swapping the recyclerView content for each fragment, but it didn't work.

Surprisingly, it loads all the data, if keep sliding to other pages and come back to 1st or 2nd page.

I tried all possible solution from my side.

Any kind suggestions would be appreciated.

Ritt
  • 3,181
  • 3
  • 22
  • 51
  • Are you sure that is your RecyclerView Adapter? – Much Overflow Feb 01 '16 at 12:23
  • @MuchOverflow Thank you for noticing, i have updated my question. Please have a look. – Ritt Feb 01 '16 at 12:32
  • I would mitigate this issue by using an EventBus (Square's Otto for example). Initially I would have the fragment open with a ProgressBar. Then I would check if the data is loaded for that particular fragment. If so I would hide the loader and show the data. If not, I will register the fragment with the EventBus and wait for it to respond once the data is loaded. – Much Overflow Feb 01 '16 at 12:48
  • @MuchOverflow:- yeah, this might work. I am using event bus only for each request, from the main activity. If i use the above approach, i don't want to block, the fragment, if i want to switch to other tab, the progresss bar should go and should start for other tab, and the response of previous one should be binded with that. In this case, there will be only one subscriber, so how bus will know which fragment to update, because the subscribe method is going to be same for all fragments. – Ritt Feb 01 '16 at 15:49
  • And also, viewPager creates item for its left and right, so let's say i am in position one, it will also make request for 2 more positions to make the page ready, i don't see how this will work. – Ritt Feb 01 '16 at 16:04
  • First, pass the position of the fragment in the view pager adapter, to the fragment itself in a bundle and maintain it as a local variable. The create a custom POJO specifically to send messages to fragments. This class would contain the position of the fragment and the data itself. After you bind the data to your hash map you wrap the result in this POJO and broadcast. If the fragment is live during this period, it will check the id and data to verifiy if the data belong to that particular fragment and move forward from there. – Much Overflow Feb 01 '16 at 16:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/102270/discussion-between-ritesh-and-much-overflow). – Ritt Feb 01 '16 at 17:20

1 Answers1

1

This is how you keep your results, where you keep this is irrelevant

private LinkedHashMap<String, List<Venue>> mVenueHashMap;

And this is how you instantiate your fragment

ShowTimeFragment showTimeFragment = ShowTimeFragment.newInstance(stringDate);

You also want the fragment to load data if it is visible to user, if I am not mistaken, So you shall do the following in your fragment

public class ShowTimeFragment extends Fragment {
    private String mStringDate; //obtained and stored here from pager adapter
    private List<Venue> yourResults; // also obtained from pager adapter

    @Override
    public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.show_time_fragment, container, false);
        ButterKnife.bind(this, view);
        //at this point the progress bar should be visible.. view initilaisations are 
        //omitted to keep the code simple
        if(yourResults != null){
            //hide progress, show data
        }

        return view;
    }

    @Override
    public void setUserVisibilityHint(boolean isVisibleToUser){
        super.setUserVisibilityHint(isVisibleToUser)
        //assuming you keep track of requests made for each dates in your activity
        if(isVisibleToUser && yourResults== null && 
              !myActivity.isRequestingForDate(mStringDate)){
             // tell your activity here to load the data for this particular date 
             // if there is no data and there is no ongoing request for current date
        }
    }

    @Override
    public void onResume(){
       super.onResume();
       EventBus.getInstance().register(this);
    }

    @Override
    public void onPause(){
       super.onPause();
       EventBus.getInstance().unregister(this);
    }

    @Subscribe
    public void onVenueReceived(VenueResponse response){
        if(mStringdate.equals(response.getDate())){
            yourResult = response.getVenues();
            // hide progressbar and show data here
        }
    }
}

and your VenueResponse class would look like this

public final class VenueResponse {
    String date;
    List<Venue> venues;

    public VenueResponse(String date, List<Venue> venues) {
       this.date = date;
       this.venues = venues;
    }

    public List<Venues> getVenues(){
        return venues;
    }

    public String getDate(){
        return date;
    }
}

in your activity, wherever you are receiving the network response, you would do the following to save and notify fragments

String date; // requested date
List<Venue> venues; // received from network call for a particular date

mVenueHashMap.put(date, venues); // saves to the hashmap you maintain

// if a fragment with this particular date is currentlly visible to user, it will 
// receive the following broadcast. if not, it will get the cached 
// response in `onCreateView()` lifecycle method when it becomes visible again

EventBus.getInstance().post(new VenueResponse(date, venues));

It is quiet a work but serves the purpose :)

Much Overflow
  • 3,142
  • 1
  • 23
  • 40