RecyclerView и NavigationUI

Возникла проблема при использовании NavigationUI. Структура приложения состоит из одного Activity и множества Fragment. При переходе с одного фрагмента (RuleListFragment) на другой (RuleFragment) и возвращении обратно на первый фрагмент (RuleListFragment) нажатием кнопки Up все данные в RecyclerView исчезают. Для обновления данных RecyclerView использую OnResume (код ниже), не помогает.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:openDrawer="start"
    android:fitsSystemWindows="true">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appBarLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:liftOnScroll="true">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_scrollFlags="scroll|enterAlways|snap"/>

        </com.google.android.material.appbar.AppBarLayout>

        <fragment
            android:id="@+id/hostFragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/navigation_graph"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/navView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/navigation_header"
        app:menu="@menu/menu_navigation"
        android:fitsSystemWindows="true"/>


fragment_recycler.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

fragment_rule.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="fragment_rule"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
public class RuleListFragment extends Fragment {
    private RecyclerView recyclerView;
    private RuleAdapter ruleAdapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_recycler, container, false);

        recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        updateRule();

        return view;
    }

    private void updateRule() {
        RuleLab ruleLab = RuleLab.get(getActivity());
        List<Rule> rules = ruleLab.getRules();

        if (ruleAdapter == null) {
            ruleAdapter = new RuleAdapter(rules);
            recyclerView.setAdapter(ruleAdapter);
        } else {
            ruleAdapter.notifyDataSetChanged();
        }
    }

    private class RuleHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private Rule rule;
        private TextView ruleTitle;

        public RuleHolder(LayoutInflater inflater, ViewGroup parent) {
            super(inflater.inflate(R.layout.recycler_item, parent, false));

            itemView.setOnClickListener(this);
            ruleTitle = (TextView) itemView.findViewById(R.id.textView5);
        }

        public void bind (Rule rule) {
            this.rule = rule;

            ruleTitle.setText(rule.getTitle());
        }

        @Override
        public void onClick(View view) {
            Navigation.findNavController(view).navigate(R.id.ruleFragment);
        }
    }

    private class RuleAdapter extends RecyclerView.Adapter<RuleHolder> {
        private List<Rule> rules;

        public RuleAdapter (List<Rule> rules) {
            this.rules = rules;
        }

        @NonNull
        @Override
        public RuleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
            return new RuleHolder(layoutInflater, parent);
        }

        @Override
        public void onBindViewHolder(@NonNull RuleHolder holder, int position) {
            Rule rule = rules.get(position);
            holder.bind(rule);
        }

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

    public void onResume() {
        super.onResume();
        updateRule();
    }
}
public class RuleFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_rule, container, false);
    }
}

Ответы (2 шт):

Автор решения: pie

В данном примере не происходит изменение Adapter поэтому ruleAdapter.notifyDataSetChanged(); не работает. Так же некорректно работает onResume. Поэтому убираем onResume, и условие в updateRule.

→ Ссылка
Автор решения: Ziens

Вы используете Navigation Component, часть AndroidX. В Navigation Component при переходе между фрагментами они пересоздаются заново. Для того, чтобы сохранить состояние фрагментов вы можете использовать другой компонент AndroidX, называемый androidx.lifecycle.ViewModel. Класс наследуемый от ViewModel сохраняет своё состояние при при пересоздании фрагментов. Чтобы хранить данные вы можете объявить внутри данного класса поле MutableLiveData, которое позволяет изменять состояние и внутри фрагмента, подписываться на изменения этого состояния. Короче, вот пример:

RuleListViewModel.java:

    public class RuleListViewModel extends ViewModel {

    private MutableLiveData<List<Rule>> ruleList = new MutableLiveData<>();

    public LiveData<List<Rule>> getRuleList() {
        return ruleList;
    }

    public void updateRule(List<Rule> newRules) {
        ruleList.setValue(newRules);
    }

}

RuleListFragment:

  public class RuleListFragment extends Fragment {

    private RecyclerView recyclerView;
    private RuleAdapter ruleAdapter;

    private RuleListViewModel viewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(RuleListViewModel.class);
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_recycler, container, false);

        recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        updateRule();

        // Подписываемся на обновления
        viewModel.getRuleList().observe(getViewLifecycleOwner(), rules -> {
            if(rules != null) {
                if (ruleAdapter == null) {
                    ruleAdapter = new RuleAdapter(rules);
                    recyclerView.setAdapter(ruleAdapter);
                } else {
                    ruleAdapter.updateRules(rules);
                }
            }
        });

        return view;
    }

    private void updateRule() {
        RuleLab ruleLab = RuleLab.get(getActivity());
        // Теперь обновляем не сам адаптер, а LivaData, на изменения которой подписан фрагмент
        viewModel.updateRule( ruleLab.getRules());
    }

    private class RuleHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private Rule rule;
        private TextView ruleTitle;

        public RuleHolder(LayoutInflater inflater, ViewGroup parent) {
            super(inflater.inflate(R.layout.recycler_item, parent, false));

            itemView.setOnClickListener(this);
            ruleTitle = (TextView) itemView.findViewById(R.id.textView5);
        }

        public void bind(Rule rule) {
            this.rule = rule;

            ruleTitle.setText(rule.getTitle());
        }

        @Override
        public void onClick(View view) {
            Navigation.findNavController(view).navigate(R.id.ruleFragment);
        }
    }

    private class RuleAdapter extends RecyclerView.Adapter<RuleHolder> {
        private List<Rule> rules;

        public RuleAdapter(List<Rule> rules) {
            this.rules = rules;
        }

        public void updateRules(List<Rule> rules) {
            this.rules = rules;
            notifyDataSetChanged();
        }

        @NonNull
        @Override
        public RuleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
            return new RuleHolder(layoutInflater, parent);
        }

        @Override
        public void onBindViewHolder(@NonNull RuleHolder holder, int position) {
            Rule rule = rules.get(position);
            holder.bind(rule);
        }

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

UPD: Если у вас не изменяются данные списка, тогда всё можно упростить:

public class RuleListFragment extends Fragment {

    private RecyclerView recyclerView;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_recycler, container, false);

        recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.setAdapter(new RuleAdapter(RuleLab.get(getActivity())));

        return view;
    }

    class RuleHolder {
        //TODO
    }

    private class RuleAdapter extends RecyclerView.Adapter<RuleHolder> {
        private final List<Rule> rules;

        RuleAdapter(List<Rule> rules) {
            this.rules = rules;
        }

        @NonNull
        @Override
        public RuleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            return new RuleHolder(LayoutInflater.from(getActivity()), parent);
        }

        @Override
        public void onBindViewHolder(@NonNull RuleHolder holder, int position) {
            holder.bind(rules.get(position));
        }

        @Override
        public int getItemCount() {
            return rules.size();
        }
    }
}
→ Ссылка