1.5 Передача данных между фрагментами и обновление адаптера. ViewModel и LiveData

В последней части мы разберем самый важный вопрос - передача данных между фрагментами, а также обновление адаптера при изменении списка фрагментов. В арсенале фрагментов нет достаточно удобного механизма startActivityForResult(), поэтому разработчики предоставляют несколько удобных и не очень вариантов передачи данных между фрагментами.

Подробно вопрос передачи данных между фрагментами вы можете найти в документации.

Мы рассмотрим самый удобный и самый "правильный" способ - использование таких составляющих Android Architecture Components как ViewModel и LiveData.

Android Architecture Components - ряд вспомогательных библиотек, которые помогают реализовать паттерн MVVM, а также призваны помочь с проектированием, тестированием и сопровождением приложений.

Компонент ViewModel - предназначен для хранения и управления данных, связанными с представлением, а также избавить от проблем, связанных с пересозданием activity во время таких операций, как поворот экрана и так далее. Более подробно мы познакомимся с этим компонентом позже, а пока нам надо знать, что мы будем хранить список контактов во ViewModel и получать доступ к объекту ViewModel из фрагментов.

Сначала создадим класс ViewModel. Обратите внимание, что наш класс наследуется от класса ViewModel. Создадим методы для добавления и удаления контакта, а также для геттер, который возвращает список с данными.

DataViewModel.java
public class DataViewModel extends ViewModel {

    private final MutableLiveData<List<Contact>> data = new MutableLiveData<>();

    public DataViewModel() {
        data.setValue(new ArrayList<>());
    }

    public MutableLiveData<List<Contact>> getData() {
        return data;
    }

    public void addContact(Contact contact) {
        List<Contact> list = data.getValue();
        list.add(contact);
        data.setValue(list);
    }

    public void removeContact(int index) {
        List<Contact> list = data.getValue();
        list.remove(index);
        data.setValue(list);
    }
}

Чтобы понять код, приведенный выше, нам необходимо разобраться с еще одним используемым компонентом - LiveData.

Компонент LiveData - предназначен для хранения объекта и разрешает подписаться на его изменения. Класс MutableLiveData является расширением LiveData, с отличием в том, что это не абстрактный класс и у него доступны методы postValue() и setValue(), которые позволяют менять значение LiveData.

Если говорить проще, то LiveData - это обертка над данными, которая позволяет "подписаться" на обновление данных. Этим свойством мы и воспользуемся - "подпишемся" на обновление списка внутри ContactsFragment и будем обновлять адаптер при обновлении списка с контактами.

Добавление нового контакта

Для начала реализуем логику добавления нового контакта в фрагментe AddContactFragment. Сначала в методе onViewCreated получим доступ к объекту ViewModel. Мы сами не создаем объект, а получаем его из класса ViewModelProvider.

AddContactFragment.java
public class AddContactFragment extends Fragment {

    private DataViewModel viewModel;
    
    ...

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

        viewModel = new ViewModelProvider(requireActivity()).get(DataViewModel.class);

        ...
    }
}

Далее, при обработке нажатия кнопки ADD, формируем объект класса Contact и вызываем метод addContact() у объекта DataViewModel, в который передаем объект нового контакта.

AddContactFragment.java
public class AddContactFragment extends Fragment {

    private DataViewModel viewModel;

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

        viewModel = new ViewModelProvider(requireActivity()).get(DataViewModel.class);

        ...

        Button addContactButton = view.findViewById(R.id.button_add);
        addContactButton.setOnClickListener(v -> {
            if (uri == null) {
                Toast.makeText(requireContext(), "Contact image not set!", Toast.LENGTH_SHORT).show();
                return;
            }

            String name = nameEditText.getText().toString();
            String email = EmailEditText.getText().toString();
            String phone = PhoneEditText.getText().toString();
 
            // Добавляем новый контакт в список
            Contact contact = new Contact(name, email, phone, uri);
            viewModel.addContact(contact);

            getActivity().onBackPressed();
        });

    }
}

Обновление адаптера и удаление контакта

Теперь перейдем в класс ContactsFragment. Для начала также получим ссылку на объект DataViewModel

ContactsFragment.java
public class ContactsFragment extends Fragment implements ContactsAdapter.DeleteItemListener {

    private DataViewModel viewModel;

    ... 
    
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        
        viewModel = new ViewModelProvider(requireActivity()).get(DataViewModel.class);
    }

}

Теперь мы должны "подписаться" на обновление MutableLiveData<List<Contact>> data, который находится внутри объекта DataViewModel.

ContactsFragment.java
public class ContactsFragment extends Fragment implements ContactsAdapter.DeleteItemListener {

    private DataViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        
        viewModel = new ViewModelProvider(requireActivity()).get(DataViewModel.class);
        viewModel.getData().observe(getViewLifecycleOwner(), contacts -> {
            adapter.notifyDataSetChanged();
        });
       
        ...
    }
}

С помощью метода getData() мы получаем доступ к объекту data, вызываем метод observe, куда передаем ссылку на сам фрагмент (концепция LifecycleOwner будет рассмотрена позже) с помощью метода getViewLifecycleOwner(), а также передаем метод с помощью лямбда-выражения. В методе мы описываем код, который будет выполнен, когда data будет обновлена. В нашем случае, мы обновляем адаптер и список с контактами будет обновлен.

Последний штрих - удаление контакта. Для этого отредактируем метод onDeleteItem()

ContactsFragment.java
public class ContactsFragment extends Fragment implements ContactsAdapter.DeleteItemListener {

    private DataViewModel viewModel;
    
    ...

    @Override
    public void onDeleteItem(int position) {
        viewModel.removeContact(position);
    }
}

Ниже приведен полный листинг классов

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setWindow();

        getSupportFragmentManager()
                .beginTransaction()
                .setReorderingAllowed(true)
                .add(R.id.container, ContactsFragment.class, null)
                .addToBackStack("main")
                .commit();
    }

    @Override
    public void onBackPressed(){
        FragmentManager fm = getSupportFragmentManager();
        if (fm.getBackStackEntryCount() > 1) {
            fm.popBackStack();
        } else {
            finish();
        }
    }

    private void setWindow() {
        // Метод устанавливает StatusBar в цвет фона
        Window window = this.getWindow();
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.setStatusBarColor(getColor(R.color.activity_background));

        View decor = getWindow().getDecorView();
        decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
    }

    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    public void verifyStoragePermissions() {
        // Проверяем наличие разрешения на запись во внешнее хранилище
        int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);

        if (permission != PackageManager.PERMISSION_GRANTED) {
            // Запрашиваем разрешение у пользователя
            ActivityCompat.requestPermissions(
                    this,
                    PERMISSIONS_STORAGE,
                    REQUEST_EXTERNAL_STORAGE
            );
        }
    }
}

Last updated