Мы решаем реализовать в приложении работу не только с БД, но и с сервером. Поэтому добавляем новый (и последний) модуль - network.
Оглавление
4. Используем Context при создании объекта
6. Фрагмент в отдельный модуль
10. Заключение и полезные ссылки
Пока что этот новый модуль не даст нам никаких новых знаний с точки зрения даггера и мультимодульности. Он будет похож на модуль data по своим связям с app и task. Но в следующих уроках разница станет видна.
В новом модуле создаем API интерфейс:
TaskApi (:network)
interface TaskApi { }
и даггер-модуль:
NetworkModule (:network)
@Module class NetworkModule { @Provides fun provideTasksApi(): TaskApi { return object : TaskApi { } } }
Т.к. мы хотим использовать это API в модуле task, то добавляем зависимость task от network
build.gradle (:task)
implementation project(path: ':network')
Давайте заодно создадим репозиторий, который будет совмещать использование БД и API:
TaskRepository (:task)
class TaskRepository( private val database: Database, private val taskApi: TaskApi ) { }
Создаем даггер-модуль, который умеет создавать репозиторий:
TaskModule (:task)
@Module class TaskModule { @Provides fun provideTaskRepository(database: Database, taskApi: TaskApi): TaskRepository { return TaskRepository(database, taskApi) } }
Во фрагмент вместо БД будем инджектить репозиторий:
TaskFragment (:task)
class TasksFragment : Fragment() { @Inject lateinit var taskRepository: TaskRepository
Чтобы компонент в модуле app смог собрать репозиторий, ему нужны классы:
- TaskRepository из task
- Database из data
- FileManager из core
- TaskApi из network
Про объекты из модулей task, data и core он уже знает. Надо добавить зависимость app от network:
build.gradle (:app)
implementation project(path: ':network')
Схема модулей теперь выглядит так:
Добавляем в компонент даггер-модули: NetworkModule и TaskModule
AppComponent (:app)
@Component(modules = [DataModule::class, CoreModule::class, NetworkModule::class, TaskModule::class]) interface AppComponent: TaskComponent { override fun injectTasksFragment(tasksFragment: TasksFragment) }
Теперь даггер сможет создать компонент, который использует модули для создания репозитория и всех необходимых для этого объектов.
Мы со своей стороны тоже подправим нашу реализацию компонента, чтобы примерно понимать, что делает даггер:
MyAppComponent (:app)
class MyAppComponent(private val context: Context) : AppComponent { private val dataModule = DataModule() private val coreModule = CoreModule() private val networkModule = NetworkModule() private val taskModule = TaskModule() override fun injectTasksFragment(tasksFragment: TasksFragment) { tasksFragment.taskRepository = taskModule.provideTaskRepository( dataModule.provideDatabase(context, coreModule.provideFileManager()), networkModule.provideTasksApi() ) } }
Создаем даггер-модули и с их помощью собираем репозиторий
Схема объектов:
Выделим основные моменты
Фрагмент использует репозиторий. А репозиторий использует TaskApi и Database. Поэтому модулю task нужны модули data и network. FileManager напрямую в репозитории не используется, поэтому модуль core не нужен
Если фрагмент просто использует репозиторий, то компоненту его приходится собирать. Для этого ему нужны TaskApi, Database и FileManager. Поэтому app зависит от data, network и core.
Фрагмент использует компонент, чтобы получить собранный объект репозитория.
Результат
На этом мы закончили добавлять модули и объекты. Далее займемся конфигурацией компонента.
Сейчас у нас используется AppComponent, который находится в модуле app. Он собирает объекты для фрагмента в модуле task.
Что дает нам такая конфигурация в случае, когда у нас много модулей, похожих на модуль task, со своими фрагментами, которые используют объекты из модулей data, core, network и т.п.?
Давайте выделим основные моменты
1) Scope
Компонент AppComponent предоставляет нам только Application scope. С ним не получится создать для объекта синглтон, время жизни которого находится в рамках какого-либо экрана. Этот синглтон будет жить все время работы приложения.
Это определенно минус текущей конфигурации. Хотелось бы иметь более гибкие scope.
2) Зависимости модулей
Модуль app должен знать абсолютно все модули, потому что компонент собирает все объекты для всех фрагментов.
Тоже можно считать минусом, особенно если модулей много.
3) Зона ответственности компонента
Компонент знает очень много. Он умеет инджектить в каждый фрагмент. Слишком большая ответственность для одного объекта.
Это тоже не очень хорошо. Такую ответственность надо бы раскидать по разным классам.
4) Размер класса компонента
Сгенерированный класс компонента получится огромным.
Некритично, но тоже неприятно.
В следующие два урока мы попробуем все это исправить. Вместо использования AppComponent мы создадим для модуля task отдельный компонент.
У нас два варианта: сделать этот компонент саб-компонентом для AppComponent или отдельным компонентом (не саб). Рассмотрим оба варианта.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня