I have a ViewPager2 which constructs / loads a fragment (localeChoosingFragment) which contains a recyclerview, using data from a viewmodel (as MutableLiveData). When the user clicks on an item in that recyclerView, I need the second tab in the ViewPager2 to update its text based on the item clicked. This information is stored in japanGuideViewModel.getCurrentLocaleBeingViewed(). This works, but when clicked, the recyclerView in localeChoosingFragment resets to the first position.
Sometimes, when I comment out a lot of code, changing the name of the tab in the ViewPager2 causes the recyclerview adaptor to go back to the first position. Other times, just changing the Livedata causes it.
I am concerned that there is something going on I don't and should know about, especially this early in the project.
Neither fragment or the recyclerview are recreated, nor is notifyDataSetChanged called.
That ViewPager 2 is actually part of a fragment that is selected from another ViewPager 2, but I don't think that's what's causing the problem.
I have tried adding setRetainInstanceState (deprecated) to true, but it didn't work.
I will attach a cleaned up version of my code.
Here is the plannerFragment:
public class PlannerFragment extends Fragment {
Context mContext;
JapanGuideViewModel japanGuideViewModel;
View plannerFragmentView;
ViewPager2 plannerFragmentViewPager2;
TabLayout tabLayout;
LocaleChoosingFragment localeChoosingFragment;
LocaleExploringFragmnet localeExploringFragment;
public static final String TAG = "JapanGuideTAG";
public PlannerFragment() {
Toast.makeText(mContext, "New Planner Fragment, empty constructor", Toast.LENGTH_SHORT).show();
}
public PlannerFragment(Context mContext, JapanGuideViewModel japanGuideViewModel) {
this.japanGuideViewModel = japanGuideViewModel;
this.mContext = mContext;
Log.d(TAG, "New Planner Fragment, arguments passed");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "oncreate in planner fragment");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "oncreateview in planner fragment");
plannerFragmentView = inflater.inflate(R.layout.fragment_planner, container, false);
return plannerFragmentView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d(TAG, "onViewCreated in PlannerFragment");
plannerFragmentViewPager2 = plannerFragmentView.findViewById(R.id.plannerFragmentViewPager2);
tabLayout = plannerFragmentView.findViewById(R.id.plannerFragmentTabLayout);
PlannerFragmentViewPager2Adaptor plannerFragmentViewPager2Adaptor = new PlannerFragmentViewPager2Adaptor(getChildFragmentManager(), getLifecycle(), mContext, japanGuideViewModel);
plannerFragmentViewPager2.setAdapter(plannerFragmentViewPager2Adaptor);
plannerFragmentViewPager2.setUserInputEnabled(false);
TabLayoutMediator.TabConfigurationStrategy tabConfigurationStrategy = new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
Log.d(TAG, "onConfigure in tabConfigurationStrategy");
if (position == 0) {
Log.d(TAG, "Chooser");
tab.setText("Explore Prefectures");
} else if (position == 1) {
tab.setText("Explore Chosen Prefecture");
}
}
};
Log.d(TAG, "attachingTabLayoutMediator");
new TabLayoutMediator(tabLayout, plannerFragmentViewPager2, tabConfigurationStrategy).attach();
Observer dataDownloadedObserver = new Observer() {
@Override
public void onChanged(Object o) {
if (japanGuideViewModel.getDataDownloaded().getValue() > 0) {
Log.d(TAG, "onChanged called in data downloaded observer. Value is " + japanGuideViewModel.getDataDownloaded().getValue());
japanGuideViewModel.getCurrentLocaleBeingViewed().setValue(0);
}
}
};
Observer localeToExploreChangedObserver = new Observer() {
@Override
public void onChanged(Object o) {
Log.d(TAG, "localeChangedObserver in planner fragment");
TabLayout.Tab tab = tabLayout.getTabAt(1);
if (tab != null) {
Log.d(TAG, "the tab is" + tab.getPosition());
// THIS IS WHERE THE PROBLEM OCCURS.
// If this line (setting the text) is removed, the recyclerview in
// localeChoosingFragment does not reset to the first position.
tab.setText(japanGuideViewModel.getLocaleNamesArray().getValue().get(japanGuideViewModel.getCurrentLocaleBeingViewed().getValue()));
}
}
};
japanGuideViewModel.getCurrentLocaleBeingViewed().observe(getViewLifecycleOwner(), localeToExploreChangedObserver);
japanGuideViewModel.getDataDownloaded().observe(getViewLifecycleOwner(), dataDownloadedObserver);
} //onViewCreated
public class PlannerFragmentViewPager2Adaptor extends FragmentStateAdapter {
Context mContext;
JapanGuideViewModel japanGuideViewModel;
public PlannerFragmentViewPager2Adaptor(FragmentManager fragmentManager, Lifecycle lifecycle, Context mContext, JapanGuideViewModel japanGuideViewModel) {
super(fragmentManager, lifecycle);
this.japanGuideViewModel = japanGuideViewModel;
this.mContext = mContext;
Log.d(TAG, "full constructor in planner fragment");
}
@NonNull
@Override
public Fragment createFragment(int position) {
Log.d(TAG, "createFragment called in plannerFragment. Position is ");
switch (position) {
case 0:
Log.d(TAG, "planner fragment, case 0, localechoosingfragment");
if(localeChoosingFragment !=null) localeChoosingFragment = new LocaleChoosingFragment(mContext, japanGuideViewModel);
return localeChoosingFragment;
case 1:
Log.d("planner fragment localeExploringFragmentTAG", "case 1");
if(localeExploringFragment !=null) localeExploringFragment = new LocaleExploringFragment(mContext, japanGuideViewModel);
return localeExploringFragment;
default:
Log.d(TAG, "default constructor so returning localeChoosingFragment");
if(localeChoosingFragment!=null) localeChoosingFragment = new LocaleChoosingFragment(mContext, japanGuideViewModel);
return localeChoosingFragment;
}
}
@Override
public int getItemCount() {
return 2;
}
}
}
And here is the localeChoosingFragment:
public class LocaleChoosingFragment extends Fragment {
JapanGuideViewModel japanGuideViewModel;
Context mContext;
View localeChoosingFragmentView;
public static final String TAG = "JapanGuideTAG";
RecyclerView localeChoosingRecyclerView;
LinearLayoutManager recyclerViewLayoutManager;
LocaleChoosingRecyclerViewAdaptor localeChoosingAdaptor;
SnapHelperOneByOne SnapHelper;
public LocaleChoosingFragment() {
Log.d(TAG, "EMPTY constructor called for localeChoosingFragment");
}
public LocaleChoosingFragment(Context mContext, JapanGuideViewModel japanGuideViewModel) {
this.japanGuideViewModel = japanGuideViewModel;
this.mContext = mContext;
Log.d(TAG, "constructor called for localeChoosingFragment");
Toast.makeText(mContext, "Creating a new localeChoosingFragment", Toast.LENGTH_SHORT).show();
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate in localeChoosingFragment");
super.onCreate(savedInstanceState);
Log.d(TAG, "creating a NEW LOCALECHOOSING RECYCLERVIEW");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.d(TAG, "onCreateView called in LocalChoosingFragment");
localeChoosingFragmentView = inflater.inflate(R.layout.fragment_locale_choosing, container, false);
localeChoosingRecyclerView = localeChoosingFragmentView.findViewById(R.id.localeChoosingRecyclerView);
return localeChoosingFragmentView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d(TAG, "onviewcreated in locale choosing fragment.");
localeChoosingAdaptor = new LocaleChoosingRecyclerViewAdaptor(mContext, japanGuideViewModel);
SnapHelper = new SnapHelperOneByOne();
recyclerViewLayoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false);
localeChoosingRecyclerView.setLayoutManager(recyclerViewLayoutManager);
localeChoosingRecyclerView.setAdapter(localeChoosingAdaptor);
SnapHelper.attachToRecyclerView(localeChoosingRecyclerView);
// localeChoosingRecyclerView.scrollToPosition(japanGuideViewModel.getCurrentLocaleBeingViewed().getValue());
}
public class LocaleChoosingRecyclerViewAdaptor extends RecyclerView.Adapter {
Context mContext;
JapanGuideViewModel japanGuideViewModel;
Button exploreNowButton;
Button wontGoHereButton;
TextView localeNameTextView;
TextView localeDescriptionTextView;
ImageView localePhotoImageView;
TextView localePhotoCaptionTextView;
public LocaleChoosingRecyclerViewAdaptor() {
Log.d(TAG, "localeChoosingAdaptor created with empty constructor");
}
public LocaleChoosingRecyclerViewAdaptor(Context mContext, JapanGuideViewModel japanGuideViewModel) {
this.mContext = mContext;
this.japanGuideViewModel = japanGuideViewModel;
Log.d(TAG, "localeChoosingAdaptor created with full constructor");
}
@NonNull
@Override
public LocaleChoosingAdaptorHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Log.d(TAG, "oncreateviewholder in localechoosingfragment");
View view = LayoutInflater.from(mContext).inflate(R.layout.locale_recyclerview_holder, parent, false);
return new LocaleChoosingAdaptorHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder called in localeChoosingFragment. The position is " + position + " and the total size is " + getItemCount());
localeNameTextView = holder.itemView.findViewById(R.id.localeRecyclerviewHolderHeadingTextview);
localeNameTextView.setText(japanGuideViewModel.getLocalesDetailsArray().getValue().get(position).getLocaleName());
localeDescriptionTextView = holder.itemView.findViewById(R.id.localeRecyclerviewHolderDescriptionTextView);
if (japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getLocaleDescription() != null) {
localeDescriptionTextView.setText(japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getLocaleDescription());
}
localePhotoImageView = holder.itemView.findViewById(R.id.localeRecyclerviewHolderImageView);
if ((japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getPhotoSet().size() != 0)) {
if ((japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getPhotoSet().get(0).getURL() != null) && (!(japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getPhotoSet().get(0).getURL().equals("")))) {
Glide.with(mContext).load(japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getPhotoSet().get(0).getURL()).into(localePhotoImageView);
}
localePhotoCaptionTextView = holder.itemView.findViewById(R.id.localeRecyclerviewHolderPhotoCaptionTextView);
if ((japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getPhotoSet().get(0).getCaption() != null) && (japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getPhotoSet().get(0).getCaption() != "")) {
localePhotoCaptionTextView.setText(japanGuideViewModel.getLocalesDetailsArray().getValue().get(holder.getAdapterPosition()).getPhotoSet().get(0).getCaption());
}
}
exploreNowButton = holder.itemView.findViewById(R.id.LocaleRecyclerviewHolderExploreNowButton);
wontGoHereButton = holder.itemView.findViewById(R.id.LocaleRecyclerviewHolderWontGoHereButton);
exploreNowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "Explore now button clicked. Setting the locale to explore to " + japanGuideViewModel.getCurrentLocaleBeingViewed().getValue());
japanGuideViewModel.getCurrentLocaleBeingViewed().setValue(holder.getAdapterPosition());
}
});
} //bind
@Override
public int getItemCount() {
return japanGuideViewModel.getLocalesDetailsArray().getValue().size();
}
} //recyclerview adaptor
public class LocaleChoosingAdaptorHolder extends RecyclerView.ViewHolder {
TextView localeNameTextView;
TextView localeDescriptionTextView;
Button exploreNowButton;
Button wontGoHereButton;
public LocaleChoosingAdaptorHolder(@NonNull View itemView) {
super(itemView);
Log.d(TAG, "localechoosing recyclerview holder constructor called");
localeNameTextView = itemView.findViewById(R.id.localeRecyclerviewHolderHeadingTextview);
localeDescriptionTextView = itemView.findViewById(R.id.localeRecyclerviewHolderDescriptionTextView);
exploreNowButton = itemView.findViewById(R.id.LocaleRecyclerviewHolderExploreNowButton);
wontGoHereButton = itemView.findViewById(R.id.LocaleRecyclerviewHolderWontGoHereButton);
}
}
}
I was not expecting the RecyclerView in LocaleChoosingFragment to reset to the first position when the observer in the plannerFragment is triggered by the observer in the plannerFragment of currentLocaleBeingViewed in the Viewmodel. Everything else works as expected.