Merge pull request #575 from vector-im/feature/fga/fix_crash_on_restore

Feature/fga/fix crash on restore
This commit is contained in:
ganfra 2023-06-12 16:13:37 +02:00 committed by GitHub
commit afb3e33f84
5 changed files with 55 additions and 39 deletions

View file

@ -23,6 +23,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
@ -39,7 +40,7 @@ private val loggerTag = LoggerTag("MainActivity")
class MainActivity : NodeComponentActivity() { class MainActivity : NodeComponentActivity() {
lateinit var mainNode: MainNode private lateinit var mainNode: MainNode
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Timber.tag(loggerTag.value).w("onCreate, with savedInstanceState: ${savedInstanceState != null}") Timber.tag(loggerTag.value).w("onCreate, with savedInstanceState: ${savedInstanceState != null}")
@ -49,26 +50,32 @@ class MainActivity : NodeComponentActivity() {
appBindings.matrixClientsHolder().restore(savedInstanceState) appBindings.matrixClientsHolder().restore(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {
ElementTheme { MainContent(appBindings)
Box( }
modifier = Modifier }
.fillMaxSize()
.background(MaterialTheme.colorScheme.background), @Composable
) { private fun MainContent(appBindings: AppBindings) {
NodeHost(integrationPoint = appyxIntegrationPoint) { ElementTheme {
MainNode( Box(
it, modifier = Modifier
appBindings.mainDaggerComponentOwner(), .fillMaxSize()
plugins = listOf( .background(MaterialTheme.colorScheme.background),
object : NodeReadyObserver<MainNode> { ) {
override fun init(node: MainNode) { NodeHost(integrationPoint = appyxIntegrationPoint) {
mainNode = node MainNode(
mainNode.handleIntent(intent) it,
} appBindings.mainDaggerComponentOwner(),
plugins = listOf(
object : NodeReadyObserver<MainNode> {
override fun init(node: MainNode) {
Timber.tag(loggerTag.value).w("onMainNodeInit")
mainNode = node
mainNode.handleIntent(intent)
} }
) }
) )
} )
} }
} }
} }
@ -80,11 +87,17 @@ class MainActivity : NodeComponentActivity() {
* - a notification is clicked. * - a notification is clicked.
* - the app is going to background (<- this is strange) * - the app is going to background (<- this is strange)
*/ */
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
Timber.tag(loggerTag.value).w("onNewIntent") Timber.tag(loggerTag.value).w("onNewIntent")
intent ?: return // If the mainNode is not init yet, keep the intent for later.
mainNode.handleIntent(intent) // It can happen when the activity is killed by the system. The methods are called in this order :
// onCreate(savedInstanceState=true) -> onNewIntent -> onResume -> onMainNodeInit
if (::mainNode.isInitialized) {
mainNode.handleIntent(intent)
} else {
setIntent(intent)
}
} }
override fun onPause() { override fun onPause() {

View file

@ -57,24 +57,24 @@ class MainNode(
DaggerComponentOwner by mainDaggerComponentOwner { DaggerComponentOwner by mainDaggerComponentOwner {
private val loggedInFlowNodeCallback = object : LoggedInFlowNode.LifecycleCallback { private val loggedInFlowNodeCallback = object : LoggedInFlowNode.LifecycleCallback {
override fun onFlowCreated(client: MatrixClient) { override fun onFlowCreated(identifier: String, client: MatrixClient) {
val component = bindings<SessionComponent.ParentBindings>().sessionComponentBuilder().client(client).build() val component = bindings<SessionComponent.ParentBindings>().sessionComponentBuilder().client(client).build()
mainDaggerComponentOwner.addComponent(client.sessionId.value, component) mainDaggerComponentOwner.addComponent(identifier, component)
} }
override fun onFlowReleased(client: MatrixClient) { override fun onFlowReleased(identifier: String, client: MatrixClient) {
mainDaggerComponentOwner.removeComponent(client.sessionId.value) mainDaggerComponentOwner.removeComponent(identifier)
} }
} }
private val roomFlowNodeCallback = object : RoomFlowNode.LifecycleCallback { private val roomFlowNodeCallback = object : RoomFlowNode.LifecycleCallback {
override fun onFlowCreated(room: MatrixRoom) { override fun onFlowCreated(identifier: String, room: MatrixRoom) {
val component = bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(room).build() val component = bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(room).build()
mainDaggerComponentOwner.addComponent(room.roomId.value, component) mainDaggerComponentOwner.addComponent(identifier, component)
} }
override fun onFlowReleased(room: MatrixRoom) { override fun onFlowReleased(identifier: String, room: MatrixRoom) {
mainDaggerComponentOwner.removeComponent(room.roomId.value) mainDaggerComponentOwner.removeComponent(identifier)
} }
} }

View file

@ -38,6 +38,10 @@ class MainDaggerComponentsOwner @Inject constructor(@ApplicationContext context:
daggerComponents.remove(identifier) daggerComponents.remove(identifier)
} }
/**
* We expose the dagger components in the opposite order they arrived.
* So we pick the most recent component when searching with the [io.element.android.libraries.architecture.bindings] methods.
*/
override val daggerComponent: Any override val daggerComponent: Any
get() = daggerComponents.values.reversed() get() = daggerComponents.values.reversed()
} }

View file

@ -64,7 +64,6 @@ import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -118,9 +117,9 @@ class LoggedInFlowNode @AssistedInject constructor(
} }
interface LifecycleCallback : NodeLifecycleCallback { interface LifecycleCallback : NodeLifecycleCallback {
fun onFlowCreated(client: MatrixClient) = Unit fun onFlowCreated(identifier: String, client: MatrixClient) = Unit
fun onFlowReleased(client: MatrixClient) = Unit fun onFlowReleased(identifier: String, client: MatrixClient) = Unit
} }
data class Inputs( data class Inputs(
@ -139,7 +138,7 @@ class LoggedInFlowNode @AssistedInject constructor(
observeAnalyticsState() observeAnalyticsState()
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { onCreate = {
plugins<LifecycleCallback>().forEach { it.onFlowCreated(inputs.matrixClient) } plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.matrixClient) }
val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory() val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory) Coil.setImageLoader(imageLoaderFactory)
inputs.matrixClient.startSync() inputs.matrixClient.startSync()
@ -151,7 +150,7 @@ class LoggedInFlowNode @AssistedInject constructor(
onDestroy = { onDestroy = {
val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory() val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory) Coil.setImageLoader(imageLoaderFactory)
plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.matrixClient) } plugins<LifecycleCallback>().forEach { it.onFlowReleased(id, inputs.matrixClient) }
appNavigationStateService.onLeavingSpace(id) appNavigationStateService.onLeavingSpace(id)
appNavigationStateService.onLeavingSession(id) appNavigationStateService.onLeavingSession(id)
loggedInFlowProcessor.stopObserving() loggedInFlowProcessor.stopObserving()

View file

@ -68,8 +68,8 @@ class RoomFlowNode @AssistedInject constructor(
) { ) {
interface LifecycleCallback : NodeLifecycleCallback { interface LifecycleCallback : NodeLifecycleCallback {
fun onFlowCreated(room: MatrixRoom) = Unit fun onFlowCreated(identifier: String, room: MatrixRoom) = Unit
fun onFlowReleased(room: MatrixRoom) = Unit fun onFlowReleased(identifier: String, room: MatrixRoom) = Unit
} }
data class Inputs( data class Inputs(
@ -83,14 +83,14 @@ class RoomFlowNode @AssistedInject constructor(
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { onCreate = {
Timber.v("OnCreate") Timber.v("OnCreate")
plugins<LifecycleCallback>().forEach { it.onFlowCreated(inputs.room) } plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.room) }
appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId) appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId)
fetchRoomMembers() fetchRoomMembers()
}, },
onDestroy = { onDestroy = {
Timber.v("OnDestroy") Timber.v("OnDestroy")
inputs.room.close() inputs.room.close()
plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.room) } plugins<LifecycleCallback>().forEach { it.onFlowReleased(id, inputs.room) }
appNavigationStateService.onLeavingRoom(id) appNavigationStateService.onLeavingRoom(id)
} }
) )