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 шт):
В данном примере не происходит изменение Adapter поэтому ruleAdapter.notifyDataSetChanged(); не работает. Так же некорректно работает onResume. Поэтому убираем onResume, и условие в updateRule.
Вы используете 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();
}
}
}