fix: make settings dialog recreation-safe
This commit is contained in:
@@ -492,6 +492,24 @@ class BoardsFlowTest {
|
||||
.check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsDialogSurvivesActivityRecreate() {
|
||||
MainActivity.dependencies.apiClientFactory = {
|
||||
FakeBoardsApiClient(
|
||||
boards = mutableListOf(BoardSummary("1", "Alpha")),
|
||||
templates = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
val scenario = ActivityScenario.launch(BoardsActivity::class.java)
|
||||
openSettingsFromDrawer()
|
||||
|
||||
scenario.recreate()
|
||||
|
||||
onView(withId(R.id.settingsFragmentContainer)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.settingsSaveAndCloseButton)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
private fun openSettingsFromDrawer() {
|
||||
onView(withId(R.id.boardsDrawerLayout)).perform(DrawerActions.open())
|
||||
onView(withId(R.id.drawerSettingsButton)).perform(click())
|
||||
|
||||
@@ -49,6 +49,15 @@ class BoardsActivity : AppCompatActivity() {
|
||||
private lateinit var apiKeyStore: ApiKeyStore
|
||||
private lateinit var apiClient: KanbnApiClient
|
||||
|
||||
internal val sessionStoreForSettingsDialog: SessionStore
|
||||
get() = sessionStore
|
||||
|
||||
internal val apiKeyStoreForSettingsDialog: ApiKeyStore
|
||||
get() = apiKeyStore
|
||||
|
||||
internal val apiClientForSettingsDialog: KanbnApiClient
|
||||
get() = apiClient
|
||||
|
||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var emptyStateText: TextView
|
||||
@@ -413,7 +422,6 @@ class BoardsActivity : AppCompatActivity() {
|
||||
val themeChanged = bundle.getBoolean(SettingsDialogFragment.RESULT_KEY_THEME_CHANGED)
|
||||
viewModel.onSettingsApplied(
|
||||
credentialsChanged = credentialsChanged,
|
||||
themeChanged = themeChanged,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -423,11 +431,7 @@ class BoardsActivity : AppCompatActivity() {
|
||||
return
|
||||
}
|
||||
SettingsDialogFragment
|
||||
.newInstance(
|
||||
sessionStore = sessionStore,
|
||||
apiClient = apiClient,
|
||||
apiKeyStore = apiKeyStore,
|
||||
)
|
||||
.newInstance()
|
||||
.show(supportFragmentManager, SettingsDialogFragment.TAG)
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class BoardsViewModel(
|
||||
fetchBoards(initial = false, refresh = true)
|
||||
}
|
||||
|
||||
fun onSettingsApplied(credentialsChanged: Boolean, themeChanged: Boolean) {
|
||||
fun onSettingsApplied(credentialsChanged: Boolean) {
|
||||
if (!credentialsChanged) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package space.hackenslacker.kanbn4droid.app.settings
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Button
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
@@ -13,29 +15,36 @@ import androidx.fragment.app.commitNow
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import space.hackenslacker.kanbn4droid.app.BoardsActivity
|
||||
import space.hackenslacker.kanbn4droid.app.MainActivity
|
||||
import space.hackenslacker.kanbn4droid.app.R
|
||||
import space.hackenslacker.kanbn4droid.app.auth.ApiKeyStore
|
||||
import space.hackenslacker.kanbn4droid.app.auth.HttpKanbnApiClient
|
||||
import space.hackenslacker.kanbn4droid.app.auth.KanbnApiClient
|
||||
import space.hackenslacker.kanbn4droid.app.auth.PreferencesApiKeyStore
|
||||
import space.hackenslacker.kanbn4droid.app.auth.SettingsApplyCoordinator
|
||||
import space.hackenslacker.kanbn4droid.app.auth.SettingsApplyResult
|
||||
import space.hackenslacker.kanbn4droid.app.auth.SessionPreferences
|
||||
import space.hackenslacker.kanbn4droid.app.auth.SessionStore
|
||||
|
||||
class SettingsDialogFragment(
|
||||
internal var sessionStore: SessionStore? = null,
|
||||
internal var apiClient: KanbnApiClient? = null,
|
||||
internal var apiKeyStore: ApiKeyStore? = null,
|
||||
internal var coordinator: SettingsApplyCoordinator? = null,
|
||||
) : DialogFragment() {
|
||||
class SettingsDialogFragment : DialogFragment() {
|
||||
lateinit var sessionStore: SessionStore
|
||||
private set
|
||||
|
||||
private lateinit var apiClient: KanbnApiClient
|
||||
private lateinit var apiKeyStore: ApiKeyStore
|
||||
|
||||
private var progress: ProgressBar? = null
|
||||
private var saveButton: Button? = null
|
||||
private var errorText: TextView? = null
|
||||
private var controlsEnabled: Boolean = true
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
resolveDependencies(context)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val resolvedSessionStore = requireNotNull(sessionStore) { "SessionStore is required" }
|
||||
val resolvedApiClient = requireNotNull(apiClient) { "KanbnApiClient is required" }
|
||||
val resolvedApiKeyStore = requireNotNull(apiKeyStore) { "ApiKeyStore is required" }
|
||||
val dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_settings, null)
|
||||
|
||||
childFragmentManager.commitNow {
|
||||
@@ -61,25 +70,21 @@ class SettingsDialogFragment(
|
||||
|
||||
dialog.setOnShowListener {
|
||||
saveButton?.setOnClickListener {
|
||||
onSaveClicked(resolvedSessionStore, resolvedApiClient, resolvedApiKeyStore)
|
||||
onSaveClicked()
|
||||
}
|
||||
setApplyInProgress(false)
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun onSaveClicked(
|
||||
sessionStore: SessionStore,
|
||||
apiClient: KanbnApiClient,
|
||||
apiKeyStore: ApiKeyStore,
|
||||
) {
|
||||
private fun onSaveClicked() {
|
||||
if (!controlsEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
val resolvedCoordinator = coordinator
|
||||
?: MainActivity.dependencies.settingsApplyCoordinatorFactory?.invoke(
|
||||
requireActivity() as androidx.appcompat.app.AppCompatActivity,
|
||||
val hostActivity = requireActivity() as AppCompatActivity
|
||||
val resolvedCoordinator = MainActivity.dependencies.settingsApplyCoordinatorFactory?.invoke(
|
||||
hostActivity,
|
||||
sessionStore,
|
||||
apiClient,
|
||||
apiKeyStore,
|
||||
@@ -177,6 +182,31 @@ class SettingsDialogFragment(
|
||||
AppCompatDelegate.setDefaultNightMode(mode)
|
||||
}
|
||||
|
||||
private fun resolveDependencies(context: Context) {
|
||||
val host = activity as? BoardsActivity
|
||||
if (host != null) {
|
||||
sessionStore = host.sessionStoreForSettingsDialog
|
||||
apiClient = host.apiClientForSettingsDialog
|
||||
apiKeyStore = host.apiKeyStoreForSettingsDialog
|
||||
return
|
||||
}
|
||||
|
||||
val appCompatActivity = activity as? AppCompatActivity
|
||||
if (appCompatActivity != null) {
|
||||
sessionStore = MainActivity.dependencies.sessionStoreFactory?.invoke(appCompatActivity)
|
||||
?: SessionPreferences(appCompatActivity.applicationContext)
|
||||
apiClient = MainActivity.dependencies.apiClientFactory?.invoke()
|
||||
?: HttpKanbnApiClient()
|
||||
apiKeyStore = MainActivity.dependencies.apiKeyStoreFactory?.invoke(appCompatActivity)
|
||||
?: PreferencesApiKeyStore(appCompatActivity)
|
||||
return
|
||||
}
|
||||
|
||||
sessionStore = SessionPreferences(context.applicationContext)
|
||||
apiClient = HttpKanbnApiClient()
|
||||
apiKeyStore = PreferencesApiKeyStore(context)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG: String = "settings_dialog"
|
||||
const val REQUEST_KEY_SETTINGS_APPLIED: String = "settings_applied_result"
|
||||
@@ -185,18 +215,8 @@ class SettingsDialogFragment(
|
||||
|
||||
private const val SETTINGS_PREFS_TAG: String = "settings_preferences_fragment"
|
||||
|
||||
fun newInstance(
|
||||
sessionStore: SessionStore,
|
||||
apiClient: KanbnApiClient,
|
||||
apiKeyStore: ApiKeyStore,
|
||||
coordinator: SettingsApplyCoordinator? = null,
|
||||
): SettingsDialogFragment {
|
||||
return SettingsDialogFragment(
|
||||
sessionStore = sessionStore,
|
||||
apiClient = apiClient,
|
||||
apiKeyStore = apiKeyStore,
|
||||
coordinator = coordinator,
|
||||
)
|
||||
fun newInstance(): SettingsDialogFragment {
|
||||
return SettingsDialogFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ class BoardsViewModelTest {
|
||||
}
|
||||
val viewModel = newViewModel(api)
|
||||
|
||||
viewModel.onSettingsApplied(credentialsChanged = true, themeChanged = false)
|
||||
viewModel.onSettingsApplied(credentialsChanged = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertTrue(api.listBoardsCalls >= 1)
|
||||
@@ -449,7 +449,7 @@ class BoardsViewModelTest {
|
||||
}
|
||||
val viewModel = newViewModel(api)
|
||||
|
||||
viewModel.onSettingsApplied(credentialsChanged = false, themeChanged = true)
|
||||
viewModel.onSettingsApplied(credentialsChanged = false)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(0, api.listBoardsCalls)
|
||||
@@ -468,7 +468,7 @@ class BoardsViewModelTest {
|
||||
val viewModel = newViewModel(api, sessionStore = sessionStore)
|
||||
|
||||
val eventDeferred = async { viewModel.events.first() }
|
||||
viewModel.onSettingsApplied(credentialsChanged = true, themeChanged = false)
|
||||
viewModel.onSettingsApplied(credentialsChanged = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
val event = eventDeferred.await()
|
||||
|
||||
Reference in New Issue
Block a user