Use a more reliable fragment id generation

This commit is contained in:
Jonas Lochmann 2023-02-13 01:00:00 +01:00
parent dac0ce74fe
commit c0d723ee00
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
3 changed files with 28 additions and 15 deletions

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,19 @@
package io.timelimit.android package io.timelimit.android
import android.app.Application import android.app.Application
import android.view.View
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
class Application : Application() { class Application : Application() {
// two legacy screens use small id numbers as they want; by running generateViewId() often enough,
// all ids that are harcoded this way are not returned from generateViewId
init { (0..1024).forEach { _ -> View.generateViewId() } }
// allocate some view ids for Fragments that are not used for anything else
// by running this in the Application class, there is a high chance that these
// are always the same ids so that there is no trouble when restoring state
val viewIdPool = (0..4).map { View.generateViewId() }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()

View file

@ -79,7 +79,6 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
private const val EXTRA_AUTH_HANDOVER = "authHandover" private const val EXTRA_AUTH_HANDOVER = "authHandover"
private const val MAIN_MODEL_STATE = "mainModelState" private const val MAIN_MODEL_STATE = "mainModelState"
private const val FRAGMENT_IDS_STATE = "fragmentIds" private const val FRAGMENT_IDS_STATE = "fragmentIds"
private const val NEXT_FRAGMENT_ID = "nextFragmentId"
private var authHandover: Triple<Long, Long, AuthenticatedUser>? = null private var authHandover: Triple<Long, Long, AuthenticatedUser>? = null
@ -114,7 +113,6 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
} }
private val mainModel by viewModels<MainModel>() private val mainModel by viewModels<MainModel>()
private var fragmentIds = mutableSetOf<Int>()
private val syncModel: SyncStatusModel by lazy { private val syncModel: SyncStatusModel by lazy {
ViewModelProviders.of(this).get(SyncStatusModel::class.java) ViewModelProviders.of(this).get(SyncStatusModel::class.java)
} }
@ -134,8 +132,7 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
if (savedInstanceState != null) { if (savedInstanceState != null) {
mainModel.state.value = savedInstanceState.getSerializable(MAIN_MODEL_STATE) as State mainModel.state.value = savedInstanceState.getSerializable(MAIN_MODEL_STATE) as State
fragmentIds.addAll(savedInstanceState.getIntegerArrayList(FRAGMENT_IDS_STATE) ?: emptyList()) mainModel.fragmentIds.addAll(savedInstanceState.getIntegerArrayList(FRAGMENT_IDS_STATE) ?: emptyList())
mainModel.nextFragmentId = savedInstanceState.getInt(NEXT_FRAGMENT_ID)
} }
lifecycleScope.launch { lifecycleScope.launch {
@ -279,7 +276,7 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
screen = screen, screen = screen,
executeCommand = ::execute, executeCommand = ::execute,
fragmentManager = supportFragmentManager, fragmentManager = supportFragmentManager,
fragmentIds = fragmentIds, fragmentIds = mainModel.fragmentIds,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
@ -300,8 +297,7 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putSerializable(MAIN_MODEL_STATE, mainModel.state.value) outState.putSerializable(MAIN_MODEL_STATE, mainModel.state.value)
outState.putIntegerArrayList(FRAGMENT_IDS_STATE, ArrayList(fragmentIds)) outState.putIntegerArrayList(FRAGMENT_IDS_STATE, ArrayList(mainModel.fragmentIds))
outState.putInt(NEXT_FRAGMENT_ID, mainModel.nextFragmentId)
} }
override fun onStart() { override fun onStart() {
@ -373,7 +369,7 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
} }
private fun cleanupFragments() { private fun cleanupFragments() {
fragmentIds mainModel.fragmentIds
.filter { fragmentId -> .filter { fragmentId ->
var v = mainModel.state.value as State? var v = mainModel.state.value as State?
@ -385,8 +381,7 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
true true
} }
.map { supportFragmentManager.findFragmentById(it) } .mapNotNull { supportFragmentManager.findFragmentById(it) }
.filterNotNull()
.filter { it.isDetached } .filter { it.isDetached }
.forEach { .forEach {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -399,7 +394,7 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
.remove(it) .remove(it)
.commitAllowingStateLoss() .commitAllowingStateLoss()
fragmentIds.remove(id) mainModel.fragmentIds.remove(id)
} }
} }

View file

@ -81,7 +81,7 @@ class MainModel(application: Application): AndroidViewModel(application) {
val activityCommand: ReceiveChannel<ActivityCommand> = activityCommandInternal val activityCommand: ReceiveChannel<ActivityCommand> = activityCommandInternal
val state = MutableStateFlow(State.LaunchState as State) val state = MutableStateFlow(State.LaunchState as State)
var nextFragmentId = 1 var fragmentIds = mutableSetOf<Int>()
val screen: Flow<Screen> = flow { val screen: Flow<Screen> = flow {
while (true) { while (true) {
@ -93,9 +93,17 @@ class MainModel(application: Application): AndroidViewModel(application) {
is State.DiagnoseScreen.DeviceOwner -> emitAll(DeviceOwnerHandling.processState(logic, scope, authenticationModelApi, state)) is State.DiagnoseScreen.DeviceOwner -> emitAll(DeviceOwnerHandling.processState(logic, scope, authenticationModelApi, state))
is FragmentState -> emitAll(state.transformWhile { is FragmentState -> emitAll(state.transformWhile {
if (it is FragmentState && it !is State.Overview) { if (it is FragmentState && it !is State.Overview) {
if (it.containerId == null) it.containerId = nextFragmentId++ val containerId = it.containerId ?: run {
((application as io.timelimit.android.Application).viewIdPool - fragmentIds).firstOrNull()?.also { id ->
it.containerId = id
}
}
emit(Screen.FragmentScreen(it, it.toolbarIcons, it.toolbarOptions, it, it.containerId!!)) if (containerId != null) {
fragmentIds.add(containerId)
emit(Screen.FragmentScreen(it, it.toolbarIcons, it.toolbarOptions, it, containerId))
}
true true
} else false } else false