-
Notifications
You must be signed in to change notification settings - Fork 75
Prevent logout when filling questionnaire #3830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,9 @@ constructor( | |
| private val authConfiguration by lazy { configService.provideAuthConfiguration() } | ||
| private var isLoginPageRendered = false | ||
|
|
||
| /** When true, suppresses automatic LoginActivity launch (e.g., during questionnaire filling) */ | ||
| var suppressLoginRedirect = false | ||
|
|
||
| fun getAccessToken(): String { | ||
| val account = findAccount() ?: return "" | ||
| val accessToken = accountManager.peekAuthToken(account, AUTH_TOKEN_TYPE) ?: "" | ||
|
|
@@ -118,12 +121,16 @@ constructor( | |
| bundle.containsKey(AccountManager.KEY_INTENT) -> { | ||
| val launchIntent = bundle.get(AccountManager.KEY_INTENT) as? Intent | ||
|
|
||
| // Deletes session PIN to allow reset | ||
| secureSharedPreference.deleteSessionPin() | ||
| if (suppressLoginRedirect) { | ||
| Timber.w("Login redirect suppressed — questionnaire in progress") | ||
| } else { | ||
| // Deletes session PIN to allow reset | ||
| secureSharedPreference.deleteSessionPin() | ||
|
|
||
| if (launchIntent != null && !isLoginPageRendered) { | ||
| context.startActivity(launchIntent.putExtra(CANCEL_BACKGROUND_SYNC, true)) | ||
| isLoginPageRendered = true | ||
| if (launchIntent != null && !isLoginPageRendered) { | ||
| context.startActivity(launchIntent.putExtra(CANCEL_BACKGROUND_SYNC, true)) | ||
| isLoginPageRendered = true | ||
| } | ||
| } | ||
|
Comment on lines
70
to
134
|
||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -121,6 +121,7 @@ | |||||
| <string name="percentage" translatable="false">%</string> | ||||||
| <string name="logging_out">Logging out. Please wait…</string> | ||||||
| <string name="session_expired">Session has been expired and must login again</string> | ||||||
|
||||||
| <string name="session_expired">Session has been expired and must login again</string> | |
| <string name="session_expired">Your session has expired. Please log in again.</string> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,13 +40,15 @@ import io.sentry.android.navigation.SentryNavigationListener | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.Instant | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.async | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.delay | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.runBlocking | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.withContext | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.hl7.fhir.r4.model.IdType | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.hl7.fhir.r4.model.QuestionnaireResponse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.configuration.app.LocationLogOptions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.data.remote.shared.TokenAuthenticator | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.datastore.ProtoDataStore | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.domain.model.LauncherType | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.rulesengine.services.LocationCoordinate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,6 +60,7 @@ import org.smartregister.fhircore.engine.ui.base.AlertDialogue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.ui.base.AlertIntent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.ui.base.BaseMultiLanguageActivity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.util.DispatcherProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.util.extension.launchActivityWithNoBackStackHistory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.util.extension.parcelable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.util.extension.serializable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.engine.util.extension.showToast | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -66,6 +69,7 @@ import org.smartregister.fhircore.engine.util.location.PermissionUtils | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.quest.R | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.quest.event.AppEvent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.quest.event.EventBus | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.quest.ui.login.LoginActivity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.quest.ui.questionnaire.QuestionnaireActivity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.quest.ui.shared.ActivityOnResultType | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.smartregister.fhircore.quest.ui.shared.ON_RESULT_TYPE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -84,6 +88,8 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Inject lateinit var dispatcherProvider: DispatcherProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Inject lateinit var tokenAuthenticator: TokenAuthenticator | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val appMainViewModel by viewModels<AppMainViewModel>() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val sentryNavListener = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SentryNavigationListener(enableNavigationBreadcrumbs = true, enableNavigationTracing = true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -110,12 +116,20 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| activityResult: ActivityResult -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val onResultType = activityResult.data?.extras?.getString(ON_RESULT_TYPE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| activityResult.resultCode == Activity.RESULT_OK && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| !onResultType.isNullOrBlank() && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lifecycleScope.launch { onSubmitQuestionnaire(activityResult) } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lifecycleScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| activityResult.resultCode == Activity.RESULT_OK && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| !onResultType.isNullOrBlank() && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onSubmitQuestionnaire(activityResult) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if session expired while questionnaire was open | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!tokenAuthenticator.isCurrentRefreshTokenActive()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| delay(3000) // Let user see data was saved | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showSessionExpiredDialog() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+120
to
+131
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | |
| activityResult.resultCode == Activity.RESULT_OK && | |
| !onResultType.isNullOrBlank() && | |
| ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE | |
| ) { | |
| onSubmitQuestionnaire(activityResult) | |
| } | |
| // Check if session expired while questionnaire was open | |
| if (!tokenAuthenticator.isCurrentRefreshTokenActive()) { | |
| delay(3000) // Let user see data was saved | |
| showSessionExpiredDialog() | |
| val isQuestionnaireResult = | |
| activityResult.resultCode == Activity.RESULT_OK && | |
| !onResultType.isNullOrBlank() && | |
| ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE | |
| if (isQuestionnaireResult) { | |
| onSubmitQuestionnaire(activityResult) | |
| // Check if session expired while questionnaire was open | |
| if (!tokenAuthenticator.isCurrentRefreshTokenActive()) { | |
| delay(3000) // Let user see data was saved | |
| showSessionExpiredDialog() | |
| } |
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The session-expiry check runs for every ActivityResult handled by startForResult, including non-questionnaire flows like the location settings intent (showLocationSettingsDialog() uses startForResult.launch(intent)). This can unexpectedly force a login prompt after returning from location settings (or any other future startForResult usage). Consider gating the refresh-token check so it only runs for questionnaire results (e.g., when onResultType == QUESTIONNAIRE, and ideally after a successful save/submit).
| } | |
| // Check if session expired while questionnaire was open | |
| if (!tokenAuthenticator.isCurrentRefreshTokenActive()) { | |
| delay(3000) // Let user see data was saved | |
| showSessionExpiredDialog() | |
| // Check if session expired while questionnaire was open | |
| if (!tokenAuthenticator.isCurrentRefreshTokenActive()) { | |
| delay(3000) // Let user see data was saved | |
| showSessionExpiredDialog() | |
| } |
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delay(3000) is a hard-coded timing value in the result handler. If this delay is meant to be consistent UX behavior, consider extracting it to a named constant (or configurable value) to make the intent clear and avoid magic numbers scattered in callbacks.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,7 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse | |
| import org.hl7.fhir.r4.model.Resource | ||
| import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig | ||
| import org.smartregister.fhircore.engine.configuration.app.LocationLogOptions | ||
| import org.smartregister.fhircore.engine.data.remote.shared.TokenAuthenticator | ||
| import org.smartregister.fhircore.engine.domain.model.ActionParameter | ||
| import org.smartregister.fhircore.engine.domain.model.isReadOnly | ||
| import org.smartregister.fhircore.engine.domain.model.isSummary | ||
|
|
@@ -79,6 +80,8 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { | |
| @Inject lateinit var dispatcherProvider: DispatcherProvider | ||
|
|
||
| @Inject lateinit var fhirParser: IParser | ||
|
|
||
| @Inject lateinit var tokenAuthenticator: TokenAuthenticator | ||
| val viewModel by viewModels<QuestionnaireViewModel>() | ||
| private lateinit var questionnaireConfig: QuestionnaireConfig | ||
| private lateinit var actionParameters: ArrayList<ActionParameter> | ||
|
|
@@ -142,6 +145,8 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { | |
| return | ||
| } | ||
|
|
||
| tokenAuthenticator.suppressLoginRedirect = true | ||
|
|
||
| viewBinding.questionnaireToolbar.setNavigationIcon(R.drawable.ic_cancel) | ||
| viewBinding.questionnaireToolbar.setNavigationOnClickListener { handleBackPress() } | ||
| viewBinding.questionnaireTitle.text = questionnaireConfig.title | ||
|
|
@@ -607,6 +612,11 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { | |
| (supportFragmentManager.findFragmentByTag(QUESTIONNAIRE_FRAGMENT_TAG) as QuestionnaireFragment?) | ||
| ?.getQuestionnaireResponse() | ||
|
|
||
| override fun onDestroy() { | ||
| tokenAuthenticator.suppressLoginRedirect = false | ||
| super.onDestroy() | ||
|
Comment on lines
148
to
+617
|
||
| } | ||
|
|
||
| companion object { | ||
|
|
||
| const val QUESTIONNAIRE_FRAGMENT_TAG = "questionnaireFragment" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there another approach that could be used instead of relying on this property of the class to know when the redirect is suppressed?