A. K. M. Tariqul Islam
A. K. M. Tariqul Islam

Reputation: 2834

One Fragment to use in multiple tabs

I receive a list (of Categories) from server and make tabs using SectionsPagerAdapter. I use one common Fragment within which I have a RecyclerView. After user logs in, I store Categories in TempData.productCategories and they are less than 15. When user selects a tab, I refresh the RecyclerView with the products of the selected Category. The issue is, currently I have 4 Categories. Only the first and the last ones have products (one product under each category). The first tab showing no product maybe because the second one is empty and Android automatically loads the second tab just after the first one. I want to see the products of the first category. Can anybody tell me what am I doing wrong?

The server gets my store ID and one store ID has less than 15 categories:

@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {

    mViewPager.setCurrentItem(tab.getPosition());

    //doing after a delay otherwise activity is null
    final int position = tab.getPosition();
    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {

            String cat  = TempData.productCategories.get(position).getName();

            Log.i("Temp", "pos: " + position + ", cat: " + cat);

            AnyItemFragment.updateList(cat, context);
        }
    }, 400);

}

Here are the other methods used in this functionality:

public static void setupListForCategory(final Activity context, final RecyclerView listView, final String category) {
    List<ProductUserModel> prods = getProductsUser(category);

    if(prods.isEmpty()) return;

    setupList(context, listView, prods);
}

And here I update the adapter:

private static void setupList(final Activity context, RecyclerView listView, List<ProductUserModel> prods){

    FastItemAdapter<ProductUserModel> p             = new FastItemAdapter<>();
    p.add(prods);
    p.withSelectable(true);
    p.withOnClickListener(new FastAdapter.OnClickListener<ProductUserModel>() {
        @Override
        public boolean onClick(final View v, final IAdapter<ProductUserModel> adapter, final ProductUserModel item, final int position) {
            PopupUtils.getUserInputQuantity(context, item, v);
            return false;
        }
    });

    //fill the recycler view
    Log.i("Temp", "updating list : " + prods.size());
    RecyclerView.LayoutManager layoutManager        = new LinearLayoutManager(context);
    listView.setLayoutManager(layoutManager);
    listView.setAdapter(p);
//        p.notifyAdapterDataSetChanged();
//        p.notifyDataSetChanged();
    listView.invalidate();
}


private static List<ProductUserModel> getProductsUser(String category) {
    List<ProductUserModel> pum                      = new ArrayList<>();

    for(int i = 0; i < TempData.productsUser.size(); i++){
        if(TempData.productsUser.get(i).getCategory().equals(category))
            pum.add(TempData.productsUser.get(i));
    }
    return pum;
}

And the sections pager adapter in the activity:

private class SectionsPagerAdapter extends android.support.v13.app.FragmentPagerAdapter {

    SectionsPagerAdapter(android.app.FragmentManager fm) {
        super(fm);
    }

    @Override
    public android.app.Fragment getItem(int position) {
        return AnyItemFragment.newInstance(position-1);
    }

    @Override
    public int getCount() {
        // Show 3 total pages.
        return TempData.productCategories.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
//            switch (position) {
            return TempData.productCategories.get(position).getName();
//                case 0:
//                    return getString(R.string.toys);
//                case 1:
//                    return getString(R.string.stationaries);
//                case 2:
//                    return getString(R.string.books);
//            }
//            return null;
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
}

Update: My fragment:

public class AnyItemFragment extends Fragment {

    static AnyItemFragment fragment;
    public static Activity context;
    private static String TAG                                  = "AllItemsFragment";


    int selectedPosition = 0;

    @BindView(R.id.listAll)
    RecyclerView listAll;

    private OnFragmentInteractionListener mListener;

    public AnyItemFragment() {
        // Required empty public constructor
    }


    public static AnyItemFragment newInstance(int categoryID) {
        fragment = new AnyItemFragment();

        fragment.selectedPosition = categoryID;
        Log.i(TAG, "sel pos: " + fragment.selectedPosition);

        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view                                       = inflater.inflate(R.layout.fragment_inv_any_item, container, false);
        ButterKnife.bind(this, view);

        context = getActivity();

        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if(!isVisibleToUser) return;
        String cat  = TempData.productCategories.get(fragment.selectedPosition).getName();

    }

    // TODO: Rename method, update argument and hook method into UI event
    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }


    interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }


    public static void updateList(String cat, Activity context){

        TempData.setupListForCategory(context, fragment.listAll, cat);
    }
}

Upvotes: 0

Views: 1836

Answers (1)

rahul.taicho
rahul.taicho

Reputation: 1379

I think for starters, you should keep the flow simple and not have a static fragment instance on which you keep calling refresh.

Your code right now is difficult to read and understand.

One basic thing to note with fragments is since they are already re-usable in tandem with a FragmentStatePagerAdapter, you almost never have to deal with this hassle of having a single static instance doing all the work.

Few problems I spotted right now for example were - You want to attach multiple fragments (as many as there are categories) so you make a new instance and update the static field

public static AnyItemFragment newInstance(int categoryID) {
    fragment = new AnyItemFragment();

    fragment.selectedPosition = categoryID;
    Log.i(TAG, "sel pos: " + fragment.selectedPosition);

    return fragment;
}

What is happening right now is since the default offscreenPageLimit on your ViewPager defaults to 1 all your updates are going to the second instance of the fragment which most probably is linked with category-2 and hence nothing renders in the first tab.

You can confirm this by adding debug break-points.

What you would ideally want to do is send the categories to your ViewPagerAdapter and based on the position set the product model list to the correct fragment itself so it knows how to render post creation.

private class SectionsPagerAdapter extends android.support.v13.app.FragmentPagerAdapter {

  private final Map<String, List<ProductUserModel>> mapOfCategoryAndProductUsers;    

  SectionsPagerAdapter(FragmentManager fm, Map<String, List<ProductUserModel>> mapOfCategoryAndProductUsers) {
      super(fm);
      this.mapOfCategoryAndProductUsers = mapOfCategoryAndProductUsers;
  }

  @Override
  public android.app.Fragment getItem(int position) {
      AnyItemFragment fragment = AnyItemFragment.newInstance(position-1);
      // Logic to map position to category...
      String category = TempData.productCategories.get(position)
      fragment.setProductUsers(mapOfCategoryAndProductUsers.get(category))
      return fragment;
  }

  @Override
  public int getCount() {
      // Show 3 total pages.
      return TempData.productCategories.size();
  }
  ...
  ...
}

And then have the render logic inside the fragment -

private List<ProductUserModel> productUsers;

public void setProductUsers(List<ProductUserModel> productUsers) {
    this.productUsers = productUsers;
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    setupList()
}

private void setupList() {
  FastItemAdapter<ProductUserModel> p = new FastItemAdapter<>();
  p.add(prods);
  p.withSelectable(true);
  p.withOnClickListener(new FastAdapter.OnClickListener<ProductUserModel>() {
    @Override
    public boolean onClick(final View v, final IAdapter<ProductUserModel> adapter, final ProductUserModel item, final int position) {
        PopupUtils.getUserInputQuantity(context, item, v);
        return false;
    }
  });

  //fill the recycler view
  Log.i("Temp", "updating list : " + prods.size());
  LayoutManager layoutManager = new LinearLayoutManager(context);
  listView.setLayoutManager(layoutManager);
  listView.setAdapter(p);
}

PS: You should avoid as much as you can the use of static methods, since they make unit testing a nightmare. Also if you don't test your code before hand, I'd suggest checking out unit tests and Espresso for instrumentation test to have more re-assurance around the working of your app and be free of regression blues.

Let me know if this helped or if you'd need more explanation

Upvotes: 1

Related Questions