Move all binding containers to a di package and normalize their method names

Every @BindingContainer now lives in a di subpackage, in a file named after
it. Containers that were declared inside a shared file (SimplePlayer.kt,
LockScreenConfig.kt, VoiceMessagePresenter.kt, TimelineItemPresenterFactories.kt,
MediaItemPresenterFactories.kt) are extracted into their own file.

Method names are made consistent too:
- @Binds functions are named bind<ConcreteParameterType>, fixing three that
  named something else (notably RoomAliasResolverBindingContainer, whose
  provider was still called providesJoinRoomPresenterFactory).
- @Provides functions are named after what they provide, and all use the
  provides prefix rather than a mix of provide and provides.

ANDROID_KEYSTORE moves back to the cryptography impl package, next to
KeyStoreSecretKeyRepository which uses it; it is not DI specific and would
otherwise be unreachable from there.

No behaviour change: Metro discovers containers via @ContributesTo and does
not care about method names, so the only call site to follow was in
DefaultVoiceRecorderTest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-08-24 11:38:11 +02:00
parent a845763f5b
commit e6116d5840
38 changed files with 204 additions and 131 deletions
@@ -20,5 +20,5 @@ import io.element.android.libraries.di.SessionScope
@BindingContainer
interface FtueBindingContainer {
@Binds
fun bindChooseSelfVerificationMethodPresenter(presenter: ChooseSelfVerificationModePresenter): Presenter<ChooseSelfVerificationModeState>
fun bindChooseSelfVerificationModePresenter(presenter: ChooseSelfVerificationModePresenter): Presenter<ChooseSelfVerificationModeState>
}
@@ -31,10 +31,10 @@ interface RoomListBindingContainer {
fun bindRoomListPresenter(presenter: RoomListPresenter): Presenter<RoomListState>
@Binds
fun bindSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState>
fun bindRoomListSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState>
@Binds
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
fun bindRoomListFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
@Binds
fun bindSpaceFiltersPresenter(presenter: SpaceFiltersPresenter): Presenter<SpaceFiltersState>
@@ -24,7 +24,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
@BindingContainer
interface InviteBindingContainer {
@Binds
fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
fun bindAcceptDeclineInvitePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
companion object {
@Provides
@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
package io.element.android.features.knockrequests.impl.data.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
@@ -14,6 +14,7 @@ import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.SingleIn
import io.element.android.features.knockrequests.api.KnockRequestPermissions
import io.element.android.features.knockrequests.api.knockRequestPermissions
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow
@@ -23,7 +24,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow
object KnockRequestsBindingContainer {
@Provides
@SingleIn(RoomScope::class)
fun knockRequestsService(room: JoinedRoom): KnockRequestsService {
fun providesKnockRequestsService(room: JoinedRoom): KnockRequestsService {
return KnockRequestsService(
knockRequestsFlow = room.knockRequestsFlow,
permissionsFlow = room.permissionsFlow(KnockRequestPermissions.DEFAULT) { perms ->
@@ -8,12 +8,7 @@
package io.element.android.features.lockscreen.impl
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import kotlin.time.Duration
import io.element.android.appconfig.LockScreenConfig as AppConfigLockScreenConfig
data class LockScreenConfig(
val isPinMandatory: Boolean,
@@ -24,18 +19,3 @@ data class LockScreenConfig(
val isStrongBiometricsEnabled: Boolean,
val isWeakBiometricsEnabled: Boolean,
)
@ContributesTo(AppScope::class)
@BindingContainer
object LockScreenConfigBindingContainer {
@Provides
fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig(
isPinMandatory = AppConfigLockScreenConfig.IS_PIN_MANDATORY,
forbiddenPinCodes = AppConfigLockScreenConfig.FORBIDDEN_PIN_CODES,
pinSize = AppConfigLockScreenConfig.PIN_SIZE,
maxPinCodeAttemptsBeforeLogout = AppConfigLockScreenConfig.MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT,
gracePeriod = AppConfigLockScreenConfig.GRACE_PERIOD,
isStrongBiometricsEnabled = AppConfigLockScreenConfig.IS_STRONG_BIOMETRICS_ENABLED,
isWeakBiometricsEnabled = AppConfigLockScreenConfig.IS_WEAK_BIOMETRICS_ENABLED,
)
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.lockscreen.impl.di
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import io.element.android.features.lockscreen.impl.LockScreenConfig
@ContributesTo(AppScope::class)
@BindingContainer
object LockScreenConfigBindingContainer {
@Provides
fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig(
isPinMandatory = io.element.android.appconfig.LockScreenConfig.IS_PIN_MANDATORY,
forbiddenPinCodes = io.element.android.appconfig.LockScreenConfig.FORBIDDEN_PIN_CODES,
pinSize = io.element.android.appconfig.LockScreenConfig.PIN_SIZE,
maxPinCodeAttemptsBeforeLogout = io.element.android.appconfig.LockScreenConfig.MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT,
gracePeriod = io.element.android.appconfig.LockScreenConfig.GRACE_PERIOD,
isStrongBiometricsEnabled = io.element.android.appconfig.LockScreenConfig.IS_STRONG_BIOMETRICS_ENABLED,
isWeakBiometricsEnabled = io.element.android.appconfig.LockScreenConfig.IS_WEAK_BIOMETRICS_ENABLED,
)
}
@@ -21,5 +21,5 @@ import io.element.android.libraries.matrix.api.timeline.Timeline
object MessagesProvidesBindingContainer {
@Provides
@LiveTimeline
fun provideLiveTimeline(joinedRoom: JoinedRoom): Timeline = joinedRoom.liveTimeline
fun providesLiveTimeline(joinedRoom: JoinedRoom): Timeline = joinedRoom.liveTimeline
}
@@ -10,28 +10,13 @@ package io.element.android.features.messages.impl.timeline.di
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.Multibinds
import dev.zacsweers.metro.SingleIn
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import kotlin.reflect.KClass
/**
* Container that declares the [TimelineItemPresenterFactory] map multi binding.
*
* Its sole purpose is to support the case of an empty map multibinding.
*/
@BindingContainer
@ContributesTo(RoomScope::class)
interface TimelineItemPresenterFactoriesBindingContainer {
@Multibinds
fun multiBindTimelineItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out TimelineItemEventContent>, TimelineItemPresenterFactory<*, *>>
}
/**
* Room level caching layer for the [TimelineItemPresenterFactory] instances.
*
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.timeline.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Multibinds
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.libraries.di.RoomScope
import kotlin.reflect.KClass
/**
* Container that declares the [TimelineItemPresenterFactory] map multi binding.
*
* Its sole purpose is to support the case of an empty map multibinding.
*/
@BindingContainer
@ContributesTo(RoomScope::class)
interface TimelineItemPresenterFactoriesBindingContainer {
@Multibinds
fun multiBindTimelineItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out TimelineItemEventContent>, TimelineItemPresenterFactory<*, *>>
}
@@ -12,27 +12,12 @@ import androidx.compose.runtime.Composable
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.IntoMap
import io.element.android.features.messages.impl.timeline.di.TimelineItemEventContentKey
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterBindingContainer {
@Binds
@IntoMap
@TimelineItemEventContentKey(TimelineItemVoiceContent::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): TimelineItemPresenterFactory<*, *>
}
@AssistedInject
class VoiceMessagePresenter(
voiceMessagePresenterFactory: VoiceMessagePresenterFactory,
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.IntoMap
import io.element.android.features.messages.impl.timeline.di.TimelineItemEventContentKey
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessagePresenter
import io.element.android.libraries.di.RoomScope
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterBindingContainer {
@Binds
@IntoMap
@TimelineItemEventContentKey(TimelineItemVoiceContent::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): TimelineItemPresenterFactory<*, *>
}
@@ -5,11 +5,13 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.preferences.impl.userstatus
package io.element.android.features.preferences.impl.userstatus.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import io.element.android.features.preferences.impl.userstatus.UserStatusPresenter
import io.element.android.features.preferences.impl.userstatus.UserStatusState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.SessionScope
@@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
@ContributesTo(SessionScope::class)
object RoomAliasResolverBindingContainer {
@Provides
fun providesJoinRoomPresenterFactory(
fun providesRoomAliasResolverPresenterFactory(
client: MatrixClient,
): RoomAliasResolverPresenter.Factory {
return object : RoomAliasResolverPresenter.Factory {
@@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
@ContributesTo(RoomScope::class)
object RoomMemberBindingContainer {
@Provides
fun provideRoomMemberDetailsPresenterFactory(
fun providesRoomMemberDetailsPresenterFactory(
room: JoinedRoom,
userProfilePresenterFactory: UserProfilePresenterFactory,
encryptionService: EncryptionService,