Refactor where Dagger Components belongs (in node)

This commit is contained in:
ganfra
2023-01-12 21:14:48 +01:00
parent 8718bc4cde
commit c3ec363e77
14 changed files with 98 additions and 193 deletions

View File

@@ -18,13 +18,9 @@ package io.element.android.x
import android.app.Application
import androidx.startup.AppInitializer
import io.element.android.x.architecture.bindings
import io.element.android.x.core.di.DaggerComponentOwner
import io.element.android.x.di.AppBindings
import io.element.android.x.di.AppComponent
import io.element.android.x.di.DaggerAppComponent
import io.element.android.x.di.SessionComponentsOwner
import io.element.android.x.initializer.CoilInitializer
import io.element.android.x.initializer.CrashInitializer
import io.element.android.x.initializer.MatrixInitializer
import io.element.android.x.initializer.MavericksInitializer
@@ -33,20 +29,17 @@ import io.element.android.x.initializer.TimberInitializer
class ElementXApplication : Application(), DaggerComponentOwner {
private lateinit var appComponent: AppComponent
private var sessionComponentsOwner: SessionComponentsOwner? = null
override val daggerComponent: Any
get() = listOfNotNull(sessionComponentsOwner?.activeSessionComponent, appComponent)
get() = appComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.factory().create(applicationContext)
sessionComponentsOwner = bindings<AppBindings>().sessionComponentsOwner()
AppInitializer.getInstance(this).apply {
initializeComponent(CrashInitializer::class.java)
initializeComponent(TimberInitializer::class.java)
initializeComponent(MatrixInitializer::class.java)
initializeComponent(CoilInitializer::class.java)
initializeComponent(MavericksInitializer::class.java)
}
}

View File

@@ -39,7 +39,6 @@ class MainActivity : NodeComponentActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ElementXTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
@@ -49,7 +48,6 @@ class MainActivity : NodeComponentActivity() {
buildContext = it,
appComponentOwner = applicationContext as DaggerComponentOwner,
matrix = appBindings.matrix(),
sessionComponentsOwner = appBindings.sessionComponentsOwner(),
rootPresenter = appBindings.rootPresenter()
)
}

View File

@@ -18,7 +18,6 @@ package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.x.matrix.Matrix
import io.element.android.x.matrix.ui.MatrixUi
import io.element.android.x.root.RootPresenter
import kotlinx.coroutines.CoroutineScope
@@ -27,6 +26,4 @@ interface AppBindings {
fun coroutineScope(): CoroutineScope
fun rootPresenter(): RootPresenter
fun matrix(): Matrix
fun matrixUi(): MatrixUi
fun sessionComponentsOwner(): SessionComponentsOwner
}

View File

@@ -26,7 +26,7 @@ import io.element.android.x.matrix.MatrixClient
@SingleIn(SessionScope::class)
@MergeSubcomponent(SessionScope::class)
interface SessionComponent: DaggerMavericksBindings, NodeFactoriesBindings {
interface SessionComponent: NodeFactoriesBindings {
fun matrixClient(): MatrixClient

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.di
import android.content.Context
import io.element.android.x.architecture.bindings
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.core.SessionId
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@SingleIn(AppScope::class)
class SessionComponentsOwner @Inject constructor(@ApplicationContext private val context: Context) {
private val sessionComponents = ConcurrentHashMap<SessionId, SessionComponent>()
var activeSessionComponent: SessionComponent? = null
private set
fun setActive(sessionId: SessionId) {
val sessionComponent = sessionComponents[sessionId]
if (activeSessionComponent != sessionComponent) {
activeSessionComponent = sessionComponent
}
}
fun create(matrixClient: MatrixClient) {
val sessionId = matrixClient.sessionId
val sessionComponent =
context.bindings<SessionComponent.ParentBindings>().sessionComponentBuilder()
.client(matrixClient).build()
sessionComponents[sessionId] = sessionComponent
setActive(sessionId)
}
fun releaseActiveSession() {
activeSessionComponent?.also {
release(it.matrixClient().sessionId)
}
}
fun release(sessionId: SessionId) {
val sessionComponent = sessionComponents.remove(sessionId)
if (activeSessionComponent == sessionComponent) {
activeSessionComponent = null
}
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.initializer
import android.content.Context
import androidx.startup.Initializer
import coil.Coil
import coil.ImageLoader
import coil.ImageLoaderFactory
import io.element.android.x.architecture.bindings
import io.element.android.x.di.AppBindings
class CoilInitializer : Initializer<Unit> {
override fun create(context: Context) {
Coil.setImageLoader(ElementImageLoaderFactory(context))
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
private class ElementImageLoaderFactory(
private val context: Context
) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
.components {
val appBindings = context.bindings<AppBindings>()
val matrixUi = appBindings.matrixUi()
val matrixClientProvider = {
appBindings
.sessionComponentsOwner().activeSessionComponent?.matrixClient()
}
matrixUi.registerCoilComponents(this, matrixClientProvider)
}
.build()
}
}

View File

@@ -3,22 +3,30 @@ package io.element.android.x.node
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import coil.Coil
import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.x.architecture.bindings
import io.element.android.x.architecture.createNode
import io.element.android.x.core.di.DaggerComponentOwner
import io.element.android.x.di.SessionComponent
import io.element.android.x.features.preferences.PreferencesFlowNode
import io.element.android.x.features.roomlist.RoomListNode
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.core.SessionId
import io.element.android.x.matrix.ui.di.MatrixUIBindings
import kotlinx.parcelize.Parcelize
class LoggedInFlowNode(
buildContext: BuildContext,
val sessionId: SessionId,
private val matrixClient: MatrixClient,
private val onOpenBugReport: () -> Unit,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.RoomList,
@@ -27,7 +35,26 @@ class LoggedInFlowNode(
) : ParentNode<LoggedInFlowNode.NavTarget>(
navModel = backstack,
buildContext = buildContext
) {
), DaggerComponentOwner {
override val daggerComponent: Any by lazy {
parent!!.bindings<SessionComponent.ParentBindings>().sessionComponentBuilder().client(matrixClient).build()
}
override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
onCreate = {
val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory)
matrixClient.startSync()
},
onDestroy = {
val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory)
}
)
}
private val roomListCallback = object : RoomListNode.Callback {
override fun onRoomClicked(roomId: RoomId) {

View File

@@ -27,6 +27,8 @@ class RoomFlowNode(
buildContext = buildContext
) {
init {
lifecycle.subscribe(
onCreate = { Timber.v("OnCreate") },

View File

@@ -9,10 +9,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.children.whenChildAttached
import com.bumble.appyx.core.clienthelper.interactor.Interactor
import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
@@ -25,13 +22,11 @@ import com.bumble.appyx.navmodel.backstack.operation.pop
import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.x.architecture.createNode
import io.element.android.x.architecture.presenterConnector
import io.element.android.x.core.compose.OnLifecycleEvent
import io.element.android.x.core.di.DaggerComponentOwner
import io.element.android.x.di.SessionComponentsOwner
import io.element.android.x.features.rageshake.bugreport.BugReportNode
import io.element.android.x.matrix.Matrix
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.core.SessionId
import io.element.android.x.root.RootEvents
import io.element.android.x.root.RootPresenter
import io.element.android.x.root.RootView
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -39,22 +34,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import timber.log.Timber
class SessionComponentsOwnerInteractor(private val sessionComponentsOwner: SessionComponentsOwner) : Interactor<RootFlowNode>() {
override fun onCreate(lifecycle: Lifecycle) {
lifecycle.subscribe(onCreate = {
whenChildAttached { commonLifecycle: Lifecycle, child: LoggedInFlowNode ->
Timber.v("LoggedInFlowNode attached: ${child.sessionId} ")
commonLifecycle.subscribe(
onDestroy = {
Timber.v("LoggedInFlowNode destroyed: ${child.sessionId}")
sessionComponentsOwner.release(child.sessionId)
}
)
}
})
}
}
import java.util.concurrent.ConcurrentHashMap
class RootFlowNode(
buildContext: BuildContext,
@@ -64,20 +44,25 @@ class RootFlowNode(
),
private val appComponentOwner: DaggerComponentOwner,
private val matrix: Matrix,
private val sessionComponentsOwner: SessionComponentsOwner,
rootPresenter: RootPresenter
) :
ParentNode<RootFlowNode.NavTarget>(
navModel = backstack,
buildContext = buildContext,
plugins = listOf(SessionComponentsOwnerInteractor(sessionComponentsOwner)),
),
DaggerComponentOwner by appComponentOwner {
private val matrixClientsHolder = ConcurrentHashMap<SessionId, MatrixClient>()
private val presenterConnector = presenterConnector(rootPresenter)
init {
override fun onBuilt() {
super.onBuilt()
whenChildAttached(LoggedInFlowNode::class) { _, child ->
child.lifecycle.subscribe(
onDestroy = { matrixClientsHolder.remove(child.sessionId) }
)
}
matrix.isLoggedIn()
.distinctUntilChanged()
.onEach { isLoggedIn ->
@@ -87,8 +72,7 @@ class RootFlowNode(
if (matrixClient == null) {
backstack.newRoot(NavTarget.NotLoggedInFlow)
} else {
matrixClient.startSync()
sessionComponentsOwner.create(matrixClient)
matrixClientsHolder[matrixClient.sessionId] = matrixClient
backstack.newRoot(NavTarget.LoggedInFlow(matrixClient.sessionId))
}
} else {
@@ -136,9 +120,12 @@ class RootFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
is NavTarget.LoggedInFlow -> {
val matrixClient =
matrixClientsHolder[navTarget.sessionId] ?: throw IllegalStateException("Makes sure to give a matrixClient with the given sessionId")
LoggedInFlowNode(
buildContext = buildContext,
sessionId = navTarget.sessionId,
matrixClient = matrixClient,
onOpenBugReport = this::onOpenBugReport
)
}

View File

@@ -57,6 +57,10 @@ class Matrix @Inject constructor(
return sessionStore.isLoggedIn()
}
suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io){
sessionStore.getLatestSession()?.sessionId()
}
suspend fun restoreSession() = withContext(coroutineDispatchers.io) {
sessionStore.getLatestSession()
?.let { session ->

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.x.matrix.ui
import coil.ComponentRegistry
import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.ui.media.MediaFetcher
import io.element.android.x.matrix.ui.media.MediaKeyer
import javax.inject.Inject
class MatrixUi @Inject constructor() {
fun registerCoilComponents(
builder: ComponentRegistry.Builder,
activeClientProvider: () -> MatrixClient?
) {
builder.add(MediaKeyer())
builder.add(MediaFetcher.Factory(activeClientProvider))
}
}

View File

@@ -0,0 +1,12 @@
package io.element.android.x.matrix.ui.di
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.x.di.SessionScope
import io.element.android.x.matrix.ui.media.LoggedInImageLoaderFactory
import io.element.android.x.matrix.ui.media.NotLoggedInImageLoaderFactory
@ContributesTo(SessionScope::class)
interface MatrixUIBindings {
fun loggedInImageLoaderFactory(): LoggedInImageLoaderFactory
fun notLoggedInImageLoaderFactory(): NotLoggedInImageLoaderFactory
}

View File

@@ -0,0 +1,34 @@
package io.element.android.x.matrix.ui.media
import android.content.Context
import coil.ImageLoader
import coil.ImageLoaderFactory
import io.element.android.x.di.ApplicationContext
import io.element.android.x.matrix.MatrixClient
import javax.inject.Inject
class LoggedInImageLoaderFactory @Inject constructor(
@ApplicationContext private val context: Context,
private val matrixClient: MatrixClient,
) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
.components {
add(MediaKeyer())
add(MediaFetcher.Factory(matrixClient))
}
.build()
}
}
class NotLoggedInImageLoaderFactory @Inject constructor(
@ApplicationContext private val context: Context,
) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
.build()
}
}

View File

@@ -37,16 +37,15 @@ internal class MediaFetcher(
return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
}
class Factory(private val activeClientProvider: () -> MatrixClient?) :
class Factory(private val client: MatrixClient) :
Fetcher.Factory<MediaResolver.Meta> {
override fun create(
data: MediaResolver.Meta,
options: Options,
imageLoader: ImageLoader
): Fetcher {
val activeClient = activeClientProvider()
return MediaFetcher(
mediaResolver = activeClient?.mediaResolver(),
mediaResolver = client.mediaResolver(),
meta = data,
options = options,
imageLoader = imageLoader