How to prevent fragment to refresh recyclerview when after back from another fragment / save recyclerview position

Viewed 968

In this part I try to add one feature in my app and fix one problem, I would like to save the recyclerview position so when click and navigate to details fragment and click back, this should back to the same position, the issue here is when I click back I see the items overlapping, there are about 8 items and it starts from next list position, while I searching for the solution of this issue, I found this answer then I decided to add the dependencies of recyclerview separately implementation "androidx.recyclerview:recyclerview:1.2.1" to use this feature

adapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);

but it doesn't work, the next GIF describe the problem

recyclerview position problem

and this example of the app with this feature

Target

HomeFragment

@AndroidEntryPoint
public class HomeFragment extends Fragment {

    private FragmentHomeBinding binding;
    private PostViewModel postViewModel;
    public static final String TAG = "HomeFragment";
    private PostAdapter adapter;
    private List<Item> itemArrayList;
    private GridLayoutManager titleLayoutManager, gridLayoutManager;
    WrapContentLinearLayoutManager layoutManager;

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

        binding.homeRecyclerView.addOnItemTouchListener(new RecyclerItemClickListener(requireContext(),
                binding.homeRecyclerView, new RecyclerItemClickListener.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {

                Item item = itemArrayList.get(position);

                if (Objects.requireNonNull
                        (Navigation.findNavController(requireView())
                                .getCurrentDestination()).getId() == R.id.nav_home) {
                    Navigation.findNavController(requireView())
                            .navigate(HomeFragmentDirections.actionNavHomeToDetailsFragment(item));
                }
            }

            @Override
            public void onLongItemClick(View view, int position) {

            }
        }));
    }


    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {


        binding = FragmentHomeBinding.inflate(inflater, container, false);
        setHasOptionsMenu(true);

        postViewModel = new ViewModelProvider(this).get(PostViewModel.class);
        postViewModel.finalURL.setValue(Constants.getBaseUrl() + "?key=" + Constants.getKEY());
        itemArrayList = new ArrayList<>();
        adapter = new PostAdapter(getContext(), itemArrayList, this, postViewModel);

        layoutManager = new WrapContentLinearLayoutManager(requireContext(),
                LinearLayoutManager.VERTICAL, false);
        titleLayoutManager = new GridLayoutManager(getContext(), 2);
        gridLayoutManager = new GridLayoutManager(getContext(), 3);


//        binding.homeRecyclerView.setAdapter(adapter);
        binding.shimmerLayout.setVisibility(View.VISIBLE);
        binding.homeRecyclerView.setVisibility(View.INVISIBLE);

        adapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);


        postViewModel.recyclerViewLayoutMT.observe(getViewLifecycleOwner(), layout -> {
            Log.w(TAG, "getSavedLayout: called");
            switch (layout) {
                case "cardLayout":
                    binding.loadMoreBtn.setVisibility(View.VISIBLE);
                    binding.homeRecyclerView.setLayoutManager(layoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
                    adapter.setViewType(0);

                    break;
                case "cardMagazineLayout":
                    binding.loadMoreBtn.setVisibility(View.VISIBLE);
                    binding.homeRecyclerView.setLayoutManager(layoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
                    adapter.setViewType(1);
                    break;
                case "titleLayout":
                    binding.loadMoreBtn.setVisibility(View.GONE);
                    binding.homeRecyclerView.setLayoutManager(titleLayoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
                    adapter.setViewType(2);
                    break;
                case "gridLayout":
                    binding.loadMoreBtn.setVisibility(View.GONE);
                    binding.homeRecyclerView.setLayoutManager(gridLayoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
                    adapter.setViewType(3);
            }
        });


        if (Utils.hasNetworkAccess(requireContext())) {

            postViewModel.getPosts();

            postViewModel.postListMutableLiveData.observe(getViewLifecycleOwner(), postList -> {
                itemArrayList.addAll(postList.getItems());
                binding.shimmerLayout.stopShimmer();
                binding.shimmerLayout.setVisibility(View.GONE);
                binding.homeRecyclerView.setVisibility(View.VISIBLE);
                adapter.notifyDataSetChanged();

                Log.e(TAG, "ItemsArrayList :" + itemArrayList.get(0).getTitle());

            });


        } else {

            binding.shimmerLayout.setVisibility(View.VISIBLE);
//            binding.shimmerLayout.startShimmer();


            if (postViewModel.getAllItemsFromDataBase == null) {

                noInternetConnectionLayout();

            } else {
//                Log.e(TAG, "RoomDB Items size :" + itemsDatabase.itemDAO().getAlItems());

                binding.shimmerLayout.stopShimmer();
                binding.shimmerLayout.setVisibility(View.GONE);
                binding.emptyView.setVisibility(View.GONE);
                binding.homeRecyclerView.setVisibility(View.VISIBLE);
                postViewModel.getAllItemsFromDataBase.observe(getViewLifecycleOwner(), items -> {
                    if (items.isEmpty()) {
                        noInternetConnectionLayout();
                    } else {
                        binding.loadMoreBtn.setVisibility(View.GONE);
                        itemArrayList.addAll(items);
                        adapter.notifyDataSetChanged();
                    }
                });

            }
        }

        postViewModel.errorCode.observe(getViewLifecycleOwner(), errorCode -> {
            if (errorCode == 400) {
                Snackbar.make(requireView(), R.string.lastPost, Snackbar.LENGTH_LONG).show();
            } else {
                binding.homeRecyclerView.setVisibility(View.INVISIBLE);
                binding.emptyView.setVisibility(View.VISIBLE);
            }
        });

        binding.loadMoreBtn.setOnClickListener(view -> {
            AlertDialog dialog = Utils.setProgressDialog(requireContext());

            postViewModel.isLoading.observe(getViewLifecycleOwner(), isLoading -> {
                if (isLoading) {
                    dialog.show();
                } else {
                    dialog.dismiss();
                }
            });

            if (Utils.hasNetworkAccess(requireContext())) {
                postViewModel.getPosts();
//                Log.w(TAG, "loadMoreBtn: " + dialog.isShowing());
            } else {
                postViewModel.isLoading.postValue(true);
                postViewModel.getAllItemsFromDataBase.getValue();
                postViewModel.isLoading.postValue(false);
            }

        });

        return binding.getRoot();

    }

    private void noInternetConnectionLayout() {
        binding.shimmerLayout.stopShimmer();
        binding.shimmerLayout.setVisibility(View.GONE);
        binding.homeRecyclerView.setVisibility(View.GONE);
        binding.emptyView.setVisibility(View.VISIBLE);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        itemArrayList.clear();
        binding = null;
    }


    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {

        inflater.inflate(R.menu.main, menu);
        super.onCreateOptionsMenu(menu, inflater);

        SearchManager searchManager = (SearchManager) requireContext().getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.app_bar_search).getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().getComponentName()));
        searchView.setQueryHint(getResources().getString(R.string.searchForPosts));

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String keyword) {
                if (keyword.isEmpty()) {
                    Snackbar.make(requireView(), "please enter keyword to search", Snackbar.LENGTH_SHORT).show();
                }
                if (Utils.hasNetworkAccess(requireContext())) {
                    itemArrayList.clear();
                    postViewModel.getItemsBySearch(keyword);
                    adapter.notifyDataSetChanged();
                } else {
                    postViewModel.getItemsBySearchInDB(keyword);
                    postViewModel.getItemsBySearchMT.observe(getViewLifecycleOwner(), items ->
                            {
                                Log.d(TAG, "onQueryTextSubmit database called");
                                itemArrayList.clear();
                                itemArrayList.addAll(items);
                                adapter.notifyDataSetChanged();
                            }
                    );
                }
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                return false;
            }
        });


        searchView.setOnCloseListener(() -> {

            if (Utils.hasNetworkAccess(requireContext())) {
                Log.d(TAG, "setOnCloseListener: called");
                itemArrayList.clear();
                binding.emptyView.setVisibility(View.GONE);
                binding.homeRecyclerView.setVisibility(View.VISIBLE);
                postViewModel.getPosts();
                adapter.notifyDataSetChanged();
            } else {
                Log.d(TAG, "setOnCloseListener: called");
                binding.emptyView.setVisibility(View.GONE);
                binding.homeRecyclerView.setVisibility(View.VISIBLE);
                postViewModel.getAllItemsFromDataBase.observe(getViewLifecycleOwner(), items ->
                        {
                            itemArrayList.addAll(items);
                            adapter.notifyDataSetChanged();
                        }
                );
            }
            return false;
        });


        postViewModel.searchError.observe(getViewLifecycleOwner(), searchError -> {
            if (searchError) {
                Toast.makeText(requireContext(),
                        "There's no posts with this keyword", Toast.LENGTH_LONG).show();
            }
        });


    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {

        if (item.getItemId() == R.id.change_layout) {
            changeAndSaveLayout();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

The ViewModel

@HiltViewModel
public class PostViewModel extends ViewModel {

    public static final String TAG = "PostViewModel";


    private final com.blogspot.abtallaldigital.data.Repository repository;
    public final MutableLiveData<PostList> postListMutableLiveData = new MutableLiveData<>();
    public final MutableLiveData<String> finalURL = new MutableLiveData<>();
    public final MutableLiveData<String> token = new MutableLiveData<>();
    public final MutableLiveData<String> label = new MutableLiveData<>();
    public final MutableLiveData<Integer> errorCode = new MutableLiveData<>();
    public final MutableLiveData<Boolean> searchError = new MutableLiveData<>();
    public final LiveData<List<Item>> getAllItemsFromDataBase;
    public final MutableLiveData<List<Item>>
            getItemsBySearchMT = new MutableLiveData<>();
    public final MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
    public final MutableLiveData<String> recyclerViewLayoutMT = new MutableLiveData<>();
    private final Utils.DataStoreRepository dataStoreRepository;
    public final MutableLiveData<Integer> currentDestination = new MutableLiveData<>();


//    public MutableLiveData<Boolean> ifAnythingWrongHappened = new MutableLiveData<>();

    @Inject
    public PostViewModel(Repository repository, Utils.DataStoreRepository dataStoreRepository) {
        this.repository = repository;
        getAllItemsFromDataBase = repository.localDataSource.getAllItems();
        this.dataStoreRepository = dataStoreRepository;
        dataStoreRepository.readLayoutFlow
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new FlowableSubscriber<String>() {
                    @Override
                    public void onSubscribe(@NonNull Subscription s) {
                        s.request(Long.MAX_VALUE);
                    }

                    @Override
                    public void onNext(String layout) {
                        if (layout != null) {
                            recyclerViewLayoutMT.setValue(layout);
                        }
                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.e(TAG, "onError: " + t.getMessage());
                        Log.e(TAG, "onError: " + t.getCause());
                    }

                    @Override
                    public void onComplete() {

                    }
                });

        dataStoreRepository.readCurrentDestination
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .firstOrError().subscribeWith(new SingleObserver<Integer>() {
            @Override
            public void onSubscribe(@NonNull Disposable d) {

            }

            @Override
            public void onSuccess(@NonNull Integer destination) {
                currentDestination.setValue(destination);
            }

            @Override
            public void onError(@NonNull Throwable e) {
                Log.e(TAG, "onError: " + e.getMessage());
            }
        });

    }

    public void saveRecyclerViewLayout(String layout) {
        dataStoreRepository.saveRecyclerViewLayout("recyclerViewLayout", layout);
    }

    public void saveCurrentDestination(int currentDestination) {
        dataStoreRepository
                .saveCurrentDestination("CURRENT_DESTINATION", currentDestination);
    }


    //    @Override
//    protected void onCleared() {
//        super.onCleared();
//        postListMutableLiveData.setValue(null);
//        finalURL.setValue(null);
//        token.setValue(null);
//    }

    public void getPosts() {
        Log.e(TAG, finalURL.getValue());

        isLoading.setValue(true);
        repository.remoteDataSource.getPostList(finalURL.getValue())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Response<PostList>>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull Response<PostList> postListResponse) {

                        if (postListResponse.isSuccessful()) {
                            if (postListResponse.body() != null
                                    && postListResponse.body().getNextPageToken() != null) {
                                Log.e(TAG, postListResponse.body().getNextPageToken());
                                token.setValue(postListResponse.body().getNextPageToken());
                                isLoading.setValue(false);
                            }
                            postListMutableLiveData.setValue(postListResponse.body());

                            for (int i = 0; i < postListResponse.body().getItems().size(); i++) {
                                repository.localDataSource.insertItem(postListResponse.body()
                                        .getItems().get(i))
                                        .subscribeOn(Schedulers.io())
                                        .observeOn(AndroidSchedulers.mainThread())
                                        .subscribe(new CompletableObserver() {
                                            @Override
                                            public void onSubscribe(@NonNull Disposable d) {

                                            }

                                            @Override
                                            public void onComplete() {

                                            }

                                            @Override
                                            public void onError(@NonNull Throwable e) {

                                            }
                                        });
                            }

                            finalURL.setValue(finalURL.getValue() + "&pageToken=" + token.getValue());
                        } else {
                            isLoading.setValue(false);
                            errorCode.setValue(postListResponse.code());
                            Log.e(TAG, "onNext: " + postListResponse.code());
                            Log.e(TAG, "onNext: " + postListResponse.errorBody());
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        isLoading.setValue(false);
                        Log.e(TAG, e.getMessage() + e.getCause());
//                        ifAnythingWrongHappened.setValue(true);
                        if (e instanceof HttpException) {
                            errorCode.setValue(((HttpException) e).code());
                        }
                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }


    public void getPostListByLabel() {

        isLoading.setValue(true);
        Log.e(TAG, finalURL.getValue());

        repository.remoteDataSource.getPostListByLabel(finalURL.getValue())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Response<com.blogspot.abtallaldigital.pojo.PostList>>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull Response<com.blogspot.abtallaldigital.pojo.PostList> postListResponse) {
                        if (postListResponse.isSuccessful()) {
                            if (postListResponse.body() != null) {
//                                Log.e(TAG, postListResponse.body().getNextPageToken());
                                token.setValue(postListResponse.body().getNextPageToken());
                            }
                            postListMutableLiveData.setValue(postListResponse.body());
                            isLoading.setValue(false);
                            finalURL.postValue(Constants.getBaseUrlPostsByLabel()
                                    + "posts?labels=" + label.getValue() + "&pageToken="
                                    + token.getValue()
                                    + "&key=" + Constants.getKEY());
                        } else {
                            isLoading.setValue(false);
                            errorCode.setValue(postListResponse.code());
                            Log.e(TAG, "onNext: " + postListResponse.code());
                            Log.e(TAG, "onNext: " + postListResponse.errorBody());
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        isLoading.setValue(false);
                        Log.e(TAG, e.getMessage() + e.getCause());
                        if (e instanceof HttpException) {
                            errorCode.setValue(((HttpException) e).code());
                        }
                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }

    public void getItemsBySearch(String keyword) {

        isLoading.setValue(true);
        searchError.setValue(false);

        String url = Constants.getBaseUrl() +
                "search?q=" + keyword + "&key=" + Constants.getKEY();

        Log.e(TAG, "getItemsBySearch: " + url);

        repository.remoteDataSource.getPostListBySearch(url)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Response<com.blogspot.abtallaldigital.pojo.PostList>>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull Response<PostList> postListResponse) {

                        if (postListResponse.isSuccessful()) {
                            if (postListResponse.body() != null
                                    && postListResponse.body().getNextPageToken() != null) {
                                Log.e(TAG, postListResponse.body().getNextPageToken());
                                token.setValue(postListResponse.body().getNextPageToken());
                                isLoading.setValue(false);
                            }
                            postListMutableLiveData.setValue(postListResponse.body());
                        } else {
                            isLoading.setValue(false);
                            searchError.setValue(true);
                            Log.e(TAG, "onNext: list is null");
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        isLoading.setValue(false);
                        Log.e(TAG, e.getMessage() + e.getCause());
//                        ifAnythingWrongHappened.setValue(true);
                        if (e instanceof HttpException) {
                            errorCode.setValue(((HttpException) e).code());
                        }
                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }

    public void getItemsBySearchInDB(String keyword) {
        Log.d(TAG, "getItemsBySearchInDB: called");
        repository.localDataSource.getItemsBySearch(keyword)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<List<com.blogspot.abtallaldigital.pojo.Item>>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull List<Item> items) {
                        if (items.isEmpty()) {
                            searchError.setValue(true);
                            Log.e(TAG, "onNext: list is empty");
                        } else {
                            getItemsBySearchMT.setValue(items);
                            Log.d(TAG, "onNext: " + items.size());
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        searchError.setValue(true);
                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }

    public void insertFavorites(FavoritesEntity favoritesEntity) {
        repository.localDataSource.insertFavorites(favoritesEntity)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new CompletableObserver() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onComplete() {

                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        Log.e(TAG, "onError: " + e.getMessage());
                    }
                });
    }

    public LiveData<List<FavoritesEntity>> getAllFavorites() {
        return repository.localDataSource.getAllFavorites();
    }

    public void deleteFavoritePost(FavoritesEntity favoritesEntity) {
        repository.localDataSource.deleteFavorite(favoritesEntity);
    }

    public void deleteAllFavorites() {
        repository.localDataSource.deleteAllFavorites()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new CompletableObserver() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onComplete() {

                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        Log.e(TAG, "onError: " + e.getMessage());
                    }
                });


    }


}
2 Answers

Try initializing the adapter in onCreate instead of onViewCreated Then you can set the adapter on recyclerview in onViewCreated.

Also use try using LiveData Event Wrapper : https://stackoverflow.com/a/51762972/9854554

LiveData Event Wrapper in Java : https://stackoverflow.com/a/56072658

It will prevent livedata observer from getting data again on fragment resume (when we subscribe again on the livedata)

you are set recycler view data in onViewCreated which called everytime, i recommend viewmodel for better lifecycle handing, use LiveData or StateFlow in viewModel to prove your data integrity

Related