Найти в Дзене

Как передавать данные между фрагментами?

Оглавление

Это одна из самых популярных задач на работе, а ещё этот вопрос регулярно задают на собеседованиях. Решила подробнее разобраться и сразу зафиксировать все способы, которые знаю.

1. Использование аргументов у фрагментов

Наверное, это самый популярный и простой способ, которые все хотя бы раз да использовали. Мы можем указать аргументы для фрагмента перед его созданием и получить их в методе onCreate() фрагмента:

И получаем так:

-2

Можно использовать не только примитивы и строки, но и более сложные типы данных (можно даже передать целого котика, но он должен быть сериализуемым).

Плюсы:

  1. Простота: не нужно создавать дополнительные классы или интерфейсы, аргументы фрагментов могут быть установлены и получены напрямую через методы фрагментов.
  2. Четкая привязка данных к фрагменту: аргументы являются свойствами фрагмента, что создает явную привязку данных к конкретному фрагменту.
  3. Сохранение и восстановление данных: аргументы автоматически сохраняются и восстанавливаются при пересоздании фрагментов, например, при повороте экрана.
  4. Поддержка типов данных: аргументы могут содержать различные типы данных, включая примитивные типы (например, целые числа, строки) и сериализуемые объекты (котики).
  5. Переносимость и повторное использование: фрагменты могут быть легко вставлены в различные активити или переиспользованы в разных частях приложения, так как они не зависят от конкретного контекста.
  6. Использование в навигации: есть в Jetpack Navigation. Я не использую эту библиотеку, так что подробнее тут уже не расскажу.

У этого способа есть несколько важных минусов ограничений:

  1. Размер данных: аргументы должны быть относительно небольшого размера (точный размер зависит от версии Android, оперативной памяти устройства и его ограничений). Мне стало интересно и я постаралась поискать более точную информацию. Одним из наиболее часто упоминаемых ограничений является максимальный размер бандла, который по умолчанию составляет около 1 МБ. В общем, рекомендуется передавать примитивные типы данных или небольшие объекты (жирного котика передать не получится). Если данные слишком большие, возникает риск TransactionTooLargeException.
  2. Сериализация: данные, передаваемые через аргументы фрагментов, должны быть сериализуемыми. Это означает, что объекты должны реализовывать интерфейс Serializable или Parcelable, чтобы их можно было сохранить и восстановить при пересоздании фрагмента.
  3. Потеря данных: аргументы не подходят для долгосрочного хранения. Если приложение полностью закрывается или уничтожается системой, то аргументы фрагментов будут потеряны.
  4. Неизменяемость: значения аргументов должны быть неизменяемыми. Поскольку аргументы сохраняются и восстанавливаются автоматически, изменение значения аргумента после его установки может привести к непредсказуемым результатам.

2. Использование общей активити (Shared Activity)

Если фрагменты находятся в одной активити, то её можно использовать для передачи данных между фрагментами.

Пример кода:

https://gist.github.com/Ladgertha/444d627da87ac438cf44b2810be0cadc
https://gist.github.com/Ladgertha/444d627da87ac438cf44b2810be0cadc

FragmentA и FragmentB используют общую MainActivity для передачи данных. FragmentA вызывает метод setData() для установки данных, а FragmentB вызывает метод getData() для получения данных.

Плюсы:

  1. Простота: фрагменты могут обращаться к активити напрямую и передавать или получать данные через ее методы или свойства.
  2. Гибкость: общая активити действует как посредник между фрагментами, что позволяет легко передавать данные/события между ними и управлять обновлениями.
  3. Поддержка переключения между фрагментами: использование общей активити упрощает переключение между фрагментами, поскольку активити может управлять состоянием и обновлениями для всех своих фрагментов.
  4. Централизация логики: использование общей активити позволяет централизовать логику и обработку данных, связанных с фрагментами, в одном месте.

У этого способа тоже есть недостатки:

  1. Привязка к конкретной активити: фрагменты, использующие общую активити, становятся зависимыми от ее наличия и структуры. Если вам понадобится использовать эти фрагменты в другой активити, то придется изменять их или реализовать дополнительную логику.
  2. Сложность межфрагментного взаимодействия: управление и отслеживание передачи данных может стать сложнее, особенно при увеличении числа фрагментов и сложности котиков, которых передаём.
  3. Ограничения масштабируемости: код фрагментов и связанной с ними логики может стать слишком сложным для поддержки и расширения.
  4. Нарушение принципа единственной ответственности: общая активити может привести к нарушению принципа единственной ответственности (Single Responsibility Principle). Она несет ответственность не только за отображение и управление активити, но и за передачу данных между фрагментами.
  5. Тесная связь между фрагментами: использование общей активити может привести к усилению связности между фрагментами, особенно если передача данных происходит между больше чем двумя фрагментами. Изменения в одном фрагменте могут потребовать изменений в других фрагментах и общей активити.

3. Использование интерфейсов обратного вызова (Callback Interfaces)

Интерфейсы обратного вызова позволяют фрагментам взаимодействовать друг с другом через общий интерфейс. Один фрагмент реализует интерфейс, а другой фрагмент использует этот интерфейс для передачи данных.

Пример кода:

https://gist.github.com/Ladgertha/16c669c54f864376eec5560f907624f7
https://gist.github.com/Ladgertha/16c669c54f864376eec5560f907624f7

В этом примере FragmentA вызывает метод onDataReceived() интерфейса DataListener для передачи данных, а FragmentB реализует этот интерфейс и получает данные.

Плюсы:

  1. Гибкость и абстрактность: интерфейсы обратного вызова позволяют абстрагироваться от конкретной реализации фрагментов и активити. Фрагменты могут определить интерфейс, который описывает необходимые методы для передачи данных, а активити может реализовать этот интерфейс и обрабатывать данные соответствующим образом.
  2. Разделение обязанностей: использование интерфейсов позволяет разделить обязанности между фрагментами и активити. Фрагменты не зависят от конкретной реализации активити и могут отправлять данные через методы интерфейса обратного вызова, а активити решает, как обработать эти данные.
  3. Возможность обмена данными в обоих направлениях: интерфейсы обратного вызова поддерживают обмен данными в обоих направлениях: фрагменты могут вызывать методы интерфейса для передачи данных активити, а активити может использовать эти методы для передачи данных обратно в фрагменты.
  4. Расширяемость: интерфейсы обеспечивают расширяемость и возможность добавления нового функционала. Если вам нужно добавить дополнительные взаимодействия или передачу данных между компонентами, вы можете расширить существующий интерфейс или создать новый интерфейс обратного вызова.
  5. Читабельность и понятность кода: использование интерфейсов обратного вызова делает код более читабельным и понятным.
  6. Тестирование: интерфейсы обратного вызова упрощают тестирование фрагментов и активити, так как можно создать фиктивную реализацию интерфейса и проверить правильность передачи данных.

Минусы:

  1. Усложнение кода: использование интерфейсов обратного вызова может привести к усложнению кода, особенно при передаче большого объема данных или в случае сложной логики взаимодействия между фрагментами. Это связано с необходимостью определения и реализации интерфейсов, а также с необходимостью поддержки и отслеживания обратных вызовов между фрагментами.
  2. Разрыв связности: при использовании интерфейсов обратного вызова фрагменты становятся взаимозависимыми, так как один фрагмент должен реализовывать интерфейс, а другой фрагмент должен иметь ссылку на этот интерфейс. Это может создать связность между фрагментами и усложнить их переиспользование или перенос в другие активити или проекты.
  3. Сложность обработки ошибок: использование интерфейсов обратного вызова может усложнить обработку ошибок, особенно если требуется передавать информацию об ошибках или принимать решения на основе результатов обратного вызова.
  4. Отладка и тестирование: использование интерфейсов обратного вызова может усложнить отладку и тестирование фрагментов, особенно в случае сложной взаимосвязи между ними.
  5. Потенциальная уязвимость: нежелательное изменение или внедрение данных может привести к неправильной работе приложения или уязвимостям (например, переполнение буфера).

4. SharedViewModel

SharedViewModel основана на использовании общей ViewModel, которая может быть общей между несколькими фрагментами.

В отличие от интерфейсов обратного вызова, SharedViewModel обеспечивает более прямой и удобный способ обмена данными между фрагментами. Каждый фрагмент может получить доступ к SharedViewModel и использовать его для чтения и записи общих данных.

Преимущества:

  1. Простота использования: SharedViewModel позволяет обмениваться данными между фрагментами без необходимости явно реализовывать интерфейсы обратного вызова или передавать данные через активити.
  2. Совместное использование данных: SharedViewModel позволяет фрагментам совместно использовать одни и те же данные, что упрощает синхронизацию состояния между фрагментами и предотвращает дублирование данных.
  3. Жизненный цикл, связанный с активити: SharedViewModel связана с жизненным циклом активити, к которой привязаны фрагменты. При пересоздании активити, например, при повороте экрана, общая ViewModel сохраняется и восстанавливается, что позволяет сохранять состояние данных между пересозданиями фрагментов.
  4. Легкая масштабируемость: SharedViewModel обеспечивает простой способ передачи данных между любым количеством фрагментов, связанных с одной активити. Я добавила этот пункт как плюс, но на практике я не встречала правильной реализации, чтобы масштабируемость действительно была легкой.

Недостатки:

  1. Разделение обязанностей: хотя SharedViewModel может быть общей для нескольких фрагментов, важно сохранять принцип единственной ответственности и не перегружать её большим объемом кода или сложной логикой. Это одна из основных причин, почему я не люблю этот способ. Я встречала проекты, где есть огромная SharedViewModel, которая используется в десятке фрагментов и добавлять что-то новое или менять там очень сложно. Это приносит боль и страдания.
  2. Обработка обновлений: если фрагменты имеют различные экземпляры SharedViewModel, важно правильно обрабатывать обновления данных во всех фрагментах, чтобы они оставались синхронизированными.
  3. Внимание к памяти: SharedViewModel хранится в памяти до тех пор, пока активити, связанная с фрагментами, не завершится. Поэтому, если в SharedViewModel хранятся большие объемы данных, это может привести к использованию большого объема памяти.
  4. Зависимость от активити: SharedViewModel привязана к активити, в которой она создается.

5. Singleton (глобальный объект для хранения данных)

Довольно популярный и простой способ, когда просто создаётся общий объект, где хранятся данные:

-5

В этом примере у нас есть CatStorage, который содержит список котиков и методы для добавления и получения котиков.

Внутри MyFragment мы создаем ссылку на синглтон CatStorage. Затем мы можем использовать методы синглтона для добавления котика в хранилище (addCatToStorage) и получения всех котиков из хранилища (getAllCatsFromStorage). В методе doSomethingWithCats мы получаем всех котиков из синглтона и выполняем необходимые действия с ними.

CatStorage создан с использованием объекта object, что гарантирует единственный экземпляр этого класса во всем приложении.

Плюсы:

  1. Централизация данных: использование синглтона позволяет централизовать данные в одном объекте, доступном из разных фрагментов. Это облегчает управление состоянием данных и предоставляет единое место для чтения и записи данных.
  2. Общий доступ: синглтон предоставляет общий доступ к данным из любого фрагмента в приложении.
  3. Упрощение передачи данных: используя синглтон, вам не нужно передавать данные между фрагментами напрямую или через интерфейсы обратного вызова. Фрагменты могут просто обратиться к синглтону и получить необходимые данные.
  4. Улучшенная модульность: синглтон позволяет достичь более модульной архитектуры, поскольку данные могут быть разделены от логики и представления фрагментов. Это облегчает тестирование и поддержку приложения.

Минусы:

  1. Глобальность: использование синглтона может привести к созданию глобального состояния, которое может быть сложно отследить и управлять. Изменения в синглтоне могут иметь влияние на все компоненты, что может усложнить понимание взаимосвязей и вызвать проблемы с управлением состоянием.
  2. Уязвимость безопасности: если данные в синглтоне не правильно управляются, это может привести к нежелательному изменению данных или внедрению некорректных данных.
  3. Зависимость от жизненного цикла: синглтон может стать зависимым от жизненного цикла приложения и может не подходить для ситуаций, где данные должны сохраняться и быть доступными после уничтожения фрагментов или перезапуска приложения.
  4. Тестирование: тестирование фрагментов, использующих синглтон, может быть сложным, особенно если синглтон содержит сложную логику или зависимости, которые затрудняют тестирование фрагментов. В таких случаях может потребоваться создание заглушек для синглтона, чтобы облегчить тестирование.
  5. Зависимость от контекста: синглтон может зависеть от контекста приложения, и если этот контекст не правильно управляется или может изменяться, то это может привести к проблемам с использованием синглтона во фрагментах.
  6. Увеличение сложности: использование синглтона может увеличить сложность кода и усложнить понимание взаимосвязей между компонентами.
  7. Неочевидная зависимость: если синглтон используется во множестве мест в приложении, его изменение может иметь непредсказуемые последствия. Это может затруднить отладку и сопровождение приложения.

Если нужно передать большие объемы данных между фрагментами:

  1. Использование базы данных: можно сохранить данные в базе данных и получить доступ к этим данным из разных фрагментов.
  2. Использование файлового хранилища: если данные являются файлами или большими объектами, можно сохранить их в файловую систему устройства и у фрагментов будет путь к файлу.
  3. Использование внешнего хранилища: можно загружать данные на сервер и обращаться к серверу с разных фрагментов. Тут минус, что обязательно должен быть доступ к сети.
  4. Использование сериализации: если данные являются котиками объектами, вы можете сериализовать их в JSON или Parcelable. Затем вы можете передавать сериализованные данные через аргументы фрагментов или через интерфейсы обратного вызова. Тут помним, что ограничение на размер всё-таки есть.

Дубль статей в телеграмме — https://t.me/android_junior

Мои заметки в телеграмме — https://t.me/android_junior_notes

P.S. весь код помог сгенерировать ChatGPT. :)