Это одна из самых популярных задач на работе, а ещё этот вопрос регулярно задают на собеседованиях. Решила подробнее разобраться и сразу зафиксировать все способы, которые знаю.
1. Использование аргументов у фрагментов
Наверное, это самый популярный и простой способ, которые все хотя бы раз да использовали. Мы можем указать аргументы для фрагмента перед его созданием и получить их в методе onCreate() фрагмента:
И получаем так:
Можно использовать не только примитивы и строки, но и более сложные типы данных (можно даже передать целого котика, но он должен быть сериализуемым).
Плюсы:
- Простота: не нужно создавать дополнительные классы или интерфейсы, аргументы фрагментов могут быть установлены и получены напрямую через методы фрагментов.
- Четкая привязка данных к фрагменту: аргументы являются свойствами фрагмента, что создает явную привязку данных к конкретному фрагменту.
- Сохранение и восстановление данных: аргументы автоматически сохраняются и восстанавливаются при пересоздании фрагментов, например, при повороте экрана.
- Поддержка типов данных: аргументы могут содержать различные типы данных, включая примитивные типы (например, целые числа, строки) и сериализуемые объекты (котики).
- Переносимость и повторное использование: фрагменты могут быть легко вставлены в различные активити или переиспользованы в разных частях приложения, так как они не зависят от конкретного контекста.
- Использование в навигации: есть в Jetpack Navigation. Я не использую эту библиотеку, так что подробнее тут уже не расскажу.
У этого способа есть несколько важных минусов ограничений:
- Размер данных: аргументы должны быть относительно небольшого размера (точный размер зависит от версии Android, оперативной памяти устройства и его ограничений). Мне стало интересно и я постаралась поискать более точную информацию. Одним из наиболее часто упоминаемых ограничений является максимальный размер бандла, который по умолчанию составляет около 1 МБ. В общем, рекомендуется передавать примитивные типы данных или небольшие объекты (жирного котика передать не получится). Если данные слишком большие, возникает риск TransactionTooLargeException.
- Сериализация: данные, передаваемые через аргументы фрагментов, должны быть сериализуемыми. Это означает, что объекты должны реализовывать интерфейс Serializable или Parcelable, чтобы их можно было сохранить и восстановить при пересоздании фрагмента.
- Потеря данных: аргументы не подходят для долгосрочного хранения. Если приложение полностью закрывается или уничтожается системой, то аргументы фрагментов будут потеряны.
- Неизменяемость: значения аргументов должны быть неизменяемыми. Поскольку аргументы сохраняются и восстанавливаются автоматически, изменение значения аргумента после его установки может привести к непредсказуемым результатам.
2. Использование общей активити (Shared Activity)
Если фрагменты находятся в одной активити, то её можно использовать для передачи данных между фрагментами.
Пример кода:
FragmentA и FragmentB используют общую MainActivity для передачи данных. FragmentA вызывает метод setData() для установки данных, а FragmentB вызывает метод getData() для получения данных.
Плюсы:
- Простота: фрагменты могут обращаться к активити напрямую и передавать или получать данные через ее методы или свойства.
- Гибкость: общая активити действует как посредник между фрагментами, что позволяет легко передавать данные/события между ними и управлять обновлениями.
- Поддержка переключения между фрагментами: использование общей активити упрощает переключение между фрагментами, поскольку активити может управлять состоянием и обновлениями для всех своих фрагментов.
- Централизация логики: использование общей активити позволяет централизовать логику и обработку данных, связанных с фрагментами, в одном месте.
У этого способа тоже есть недостатки:
- Привязка к конкретной активити: фрагменты, использующие общую активити, становятся зависимыми от ее наличия и структуры. Если вам понадобится использовать эти фрагменты в другой активити, то придется изменять их или реализовать дополнительную логику.
- Сложность межфрагментного взаимодействия: управление и отслеживание передачи данных может стать сложнее, особенно при увеличении числа фрагментов и сложности котиков, которых передаём.
- Ограничения масштабируемости: код фрагментов и связанной с ними логики может стать слишком сложным для поддержки и расширения.
- Нарушение принципа единственной ответственности: общая активити может привести к нарушению принципа единственной ответственности (Single Responsibility Principle). Она несет ответственность не только за отображение и управление активити, но и за передачу данных между фрагментами.
- Тесная связь между фрагментами: использование общей активити может привести к усилению связности между фрагментами, особенно если передача данных происходит между больше чем двумя фрагментами. Изменения в одном фрагменте могут потребовать изменений в других фрагментах и общей активити.
3. Использование интерфейсов обратного вызова (Callback Interfaces)
Интерфейсы обратного вызова позволяют фрагментам взаимодействовать друг с другом через общий интерфейс. Один фрагмент реализует интерфейс, а другой фрагмент использует этот интерфейс для передачи данных.
Пример кода:
В этом примере FragmentA вызывает метод onDataReceived() интерфейса DataListener для передачи данных, а FragmentB реализует этот интерфейс и получает данные.
Плюсы:
- Гибкость и абстрактность: интерфейсы обратного вызова позволяют абстрагироваться от конкретной реализации фрагментов и активити. Фрагменты могут определить интерфейс, который описывает необходимые методы для передачи данных, а активити может реализовать этот интерфейс и обрабатывать данные соответствующим образом.
- Разделение обязанностей: использование интерфейсов позволяет разделить обязанности между фрагментами и активити. Фрагменты не зависят от конкретной реализации активити и могут отправлять данные через методы интерфейса обратного вызова, а активити решает, как обработать эти данные.
- Возможность обмена данными в обоих направлениях: интерфейсы обратного вызова поддерживают обмен данными в обоих направлениях: фрагменты могут вызывать методы интерфейса для передачи данных активити, а активити может использовать эти методы для передачи данных обратно в фрагменты.
- Расширяемость: интерфейсы обеспечивают расширяемость и возможность добавления нового функционала. Если вам нужно добавить дополнительные взаимодействия или передачу данных между компонентами, вы можете расширить существующий интерфейс или создать новый интерфейс обратного вызова.
- Читабельность и понятность кода: использование интерфейсов обратного вызова делает код более читабельным и понятным.
- Тестирование: интерфейсы обратного вызова упрощают тестирование фрагментов и активити, так как можно создать фиктивную реализацию интерфейса и проверить правильность передачи данных.
Минусы:
- Усложнение кода: использование интерфейсов обратного вызова может привести к усложнению кода, особенно при передаче большого объема данных или в случае сложной логики взаимодействия между фрагментами. Это связано с необходимостью определения и реализации интерфейсов, а также с необходимостью поддержки и отслеживания обратных вызовов между фрагментами.
- Разрыв связности: при использовании интерфейсов обратного вызова фрагменты становятся взаимозависимыми, так как один фрагмент должен реализовывать интерфейс, а другой фрагмент должен иметь ссылку на этот интерфейс. Это может создать связность между фрагментами и усложнить их переиспользование или перенос в другие активити или проекты.
- Сложность обработки ошибок: использование интерфейсов обратного вызова может усложнить обработку ошибок, особенно если требуется передавать информацию об ошибках или принимать решения на основе результатов обратного вызова.
- Отладка и тестирование: использование интерфейсов обратного вызова может усложнить отладку и тестирование фрагментов, особенно в случае сложной взаимосвязи между ними.
- Потенциальная уязвимость: нежелательное изменение или внедрение данных может привести к неправильной работе приложения или уязвимостям (например, переполнение буфера).
4. SharedViewModel
SharedViewModel основана на использовании общей ViewModel, которая может быть общей между несколькими фрагментами.
В отличие от интерфейсов обратного вызова, SharedViewModel обеспечивает более прямой и удобный способ обмена данными между фрагментами. Каждый фрагмент может получить доступ к SharedViewModel и использовать его для чтения и записи общих данных.
Преимущества:
- Простота использования: SharedViewModel позволяет обмениваться данными между фрагментами без необходимости явно реализовывать интерфейсы обратного вызова или передавать данные через активити.
- Совместное использование данных: SharedViewModel позволяет фрагментам совместно использовать одни и те же данные, что упрощает синхронизацию состояния между фрагментами и предотвращает дублирование данных.
- Жизненный цикл, связанный с активити: SharedViewModel связана с жизненным циклом активити, к которой привязаны фрагменты. При пересоздании активити, например, при повороте экрана, общая ViewModel сохраняется и восстанавливается, что позволяет сохранять состояние данных между пересозданиями фрагментов.
- Легкая масштабируемость: SharedViewModel обеспечивает простой способ передачи данных между любым количеством фрагментов, связанных с одной активити. Я добавила этот пункт как плюс, но на практике я не встречала правильной реализации, чтобы масштабируемость действительно была легкой.
Недостатки:
- Разделение обязанностей: хотя SharedViewModel может быть общей для нескольких фрагментов, важно сохранять принцип единственной ответственности и не перегружать её большим объемом кода или сложной логикой. Это одна из основных причин, почему я не люблю этот способ. Я встречала проекты, где есть огромная SharedViewModel, которая используется в десятке фрагментов и добавлять что-то новое или менять там очень сложно. Это приносит боль и страдания.
- Обработка обновлений: если фрагменты имеют различные экземпляры SharedViewModel, важно правильно обрабатывать обновления данных во всех фрагментах, чтобы они оставались синхронизированными.
- Внимание к памяти: SharedViewModel хранится в памяти до тех пор, пока активити, связанная с фрагментами, не завершится. Поэтому, если в SharedViewModel хранятся большие объемы данных, это может привести к использованию большого объема памяти.
- Зависимость от активити: SharedViewModel привязана к активити, в которой она создается.
5. Singleton (глобальный объект для хранения данных)
Довольно популярный и простой способ, когда просто создаётся общий объект, где хранятся данные:
В этом примере у нас есть CatStorage, который содержит список котиков и методы для добавления и получения котиков.
Внутри MyFragment мы создаем ссылку на синглтон CatStorage. Затем мы можем использовать методы синглтона для добавления котика в хранилище (addCatToStorage) и получения всех котиков из хранилища (getAllCatsFromStorage). В методе doSomethingWithCats мы получаем всех котиков из синглтона и выполняем необходимые действия с ними.
CatStorage создан с использованием объекта object, что гарантирует единственный экземпляр этого класса во всем приложении.
Плюсы:
- Централизация данных: использование синглтона позволяет централизовать данные в одном объекте, доступном из разных фрагментов. Это облегчает управление состоянием данных и предоставляет единое место для чтения и записи данных.
- Общий доступ: синглтон предоставляет общий доступ к данным из любого фрагмента в приложении.
- Упрощение передачи данных: используя синглтон, вам не нужно передавать данные между фрагментами напрямую или через интерфейсы обратного вызова. Фрагменты могут просто обратиться к синглтону и получить необходимые данные.
- Улучшенная модульность: синглтон позволяет достичь более модульной архитектуры, поскольку данные могут быть разделены от логики и представления фрагментов. Это облегчает тестирование и поддержку приложения.
Минусы:
- Глобальность: использование синглтона может привести к созданию глобального состояния, которое может быть сложно отследить и управлять. Изменения в синглтоне могут иметь влияние на все компоненты, что может усложнить понимание взаимосвязей и вызвать проблемы с управлением состоянием.
- Уязвимость безопасности: если данные в синглтоне не правильно управляются, это может привести к нежелательному изменению данных или внедрению некорректных данных.
- Зависимость от жизненного цикла: синглтон может стать зависимым от жизненного цикла приложения и может не подходить для ситуаций, где данные должны сохраняться и быть доступными после уничтожения фрагментов или перезапуска приложения.
- Тестирование: тестирование фрагментов, использующих синглтон, может быть сложным, особенно если синглтон содержит сложную логику или зависимости, которые затрудняют тестирование фрагментов. В таких случаях может потребоваться создание заглушек для синглтона, чтобы облегчить тестирование.
- Зависимость от контекста: синглтон может зависеть от контекста приложения, и если этот контекст не правильно управляется или может изменяться, то это может привести к проблемам с использованием синглтона во фрагментах.
- Увеличение сложности: использование синглтона может увеличить сложность кода и усложнить понимание взаимосвязей между компонентами.
- Неочевидная зависимость: если синглтон используется во множестве мест в приложении, его изменение может иметь непредсказуемые последствия. Это может затруднить отладку и сопровождение приложения.
Если нужно передать большие объемы данных между фрагментами:
- Использование базы данных: можно сохранить данные в базе данных и получить доступ к этим данным из разных фрагментов.
- Использование файлового хранилища: если данные являются файлами или большими объектами, можно сохранить их в файловую систему устройства и у фрагментов будет путь к файлу.
- Использование внешнего хранилища: можно загружать данные на сервер и обращаться к серверу с разных фрагментов. Тут минус, что обязательно должен быть доступ к сети.
- Использование сериализации: если данные являются
котикамиобъектами, вы можете сериализовать их в JSON или Parcelable. Затем вы можете передавать сериализованные данные через аргументы фрагментов или через интерфейсы обратного вызова. Тут помним, что ограничение на размер всё-таки есть.
Дубль статей в телеграмме — https://t.me/android_junior
Мои заметки в телеграмме — https://t.me/android_junior_notes
P.S. весь код помог сгенерировать ChatGPT. :)