I am implementing a function that restricts an application for 10 minutes when a user inputs incorrect codes more than 5 times. I have implemented for the timer not to be initialized when they restart the application. When they restart the application, they cannot click any button but can see a dialog for 10 minutes. It's okay to know which function to use or just explain me briefly. All help will be greatly appreciated.
I have tried
I stored the start time in sharedPreference
and subtracted it from System.currentTimeMillis
. So, when the result is more than 600000 (10min), they can finally start using the application.
My question
- I would like to know how to keep showing a dialog when the application is restarted in my code.
My fragment
private lateinit var binding : FragmentHelpBinding
private lateinit var viewModel : HelpViewModel
private lateinit var mContext: MainActivity
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context as MainActivity
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_help, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(HelpViewModel::class.java)
binding.viewModel = viewModel
otpTimeLimit()
}
private fun otpTimeLimit() = runBlocking {
val currentTime = System.currentTimeMillis()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
var startTime = currentTime
var pref: SharedPreferences? = requireContext().getSharedPreferences("otpTrialLimit", MODE_PRIVATE)
pref?.edit()?.putLong("startTime", startTime)
pref?.edit()?.commit()
if (System.currentTimeMillis() - startTime <600000) {
// dialog will show up here and should last for 10 min
clickDialog()
}else if (System.currentTimeMillis() - startTime >=600000) {
// and when 10 minutes passed, it should dismiss
}
}
private fun clickDialog() {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.help_dialog, null)
var alertDialog = AlertDialog.Builder(requireContext(), R.style.CustomAlertDialog)
.setView(view)
.create()
val titleText : TextView = view.findViewById(R.id.title_text_dialog)
val contentText : TextView = view.findViewById(R.id.content_text_dialog)
binding.btnGoSearch.setOnClickListener {
titleText.text = getString(R.string.help_notification)
contentText.text = getString(R.string.notification_content)
alertDialog.show()
}
}
CodePudding user response:
Here's one strategy for doing it. I assume you want this error state to persist even if the user closes and reopens the app, so the count and the time both need to be persisted.
The strategy here is to handle most of the logic in the view model to keep track of the state and persist it. In the Fragment, you only need to observer the live data: show the dialog if necessary during lockout state, and hide the dialog if it's open when the state transitions to not locked out.
ViewModel:
class HelpViewModel(application: Application): AndroidViewModel(application) {
companion object {
private const val SHARED_PREFS_NAME = "HelpViewModelPrefs"
private const val KEY_LOCKOUT_START_TIME = "lockoutStartTime"
private const val KEY_ERROR_COUNT = "errorCount"
private const val LOCKOUT_DURATION_MILLIS = 60_000L
}
private val sharedPrefs = application.applicationContext.getSharedPreferences(
SHARED_PREFS_NAME, Context.MODE_PRIVATE
)
private val _isErrorState = MutableLiveData<Boolean>()
val isErrorState: LiveData<Boolean> get() = _isErrorState
// Set up this property so it automatically persists and is initialized
// with any previously persisted value.
private var errorCount: Int = sharedPreferences.getInt(KEY_ERROR_COUNT, 0)
set(value) {
field = value
sharedPreferences.edit {
putInt(KEY_ERROR_COUNT, value)
}
}
init {
// Figure out if we are already locked out and set state appropriately.
// If we are locked out, schedule when to end locked out state.
val lockoutStartTime = sharedPrefs.getLong(KEY_LOCKOUT_START_TIME, 0)
val lockoutEndTime = lockoutStartTime LOCKOUT_DURATION_MILLIS
val now = System.currentTimeMillis()
if (now < lockoutEndTime) {
isErrorState.value = true
viewModelScope.launch {
delay(lockoutEndTime - now)
isErrorState.value = false
}
} else {
isErrorState.value = false
}
}
// call when the user performs an error
fun incrementErrorCount() {
errorCount
if (errorCount >= 5) {
// Reset count, initiate lockout state, and schedule end of state
errorCount = 0
sharedPrefs.edit {
putLong(KEY_LOCKOUT_START_TIME, System.currentTimeMillis())
}
isErrorState.value = true
viewModelScope.launch {
delay(LOCKOUT_DURATION_MILLIS)
isErrorState.value = false
}
}
}
}
In the fragment, we handle the state by observing the live data. We keep a reference to any currently open dialog so we can automatically dismiss it if the state transitions while it is open. We null out references when the Fragment is destroyed so we don't leak them.
private var lockoutDialog: AlertDialog? = null
private val viewModel: HelpViewModel by viewModels()
private var shouldShowLockoutDialog = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ...
viewModel.isErrorState.observe(viewLifecycleOwner) { isErrorState ->
shouldShowLockoutDialog = isErrorState
if (!isErrorState) {
lockoutDialog?.dismiss()
}
}
binding.btnGoSearch.setOnClickListener {
if (shouldShowLockoutDialog) {
showLockoutDialog()
} else {
// TODO some behavior when not locked out
}
}
}
private fun showLockoutDialog() {
val layoutInflater = LayoutInflater.from(requireContext())
val view = layoutInflater.inflate(R.layout.help_dialog, null).apply {
findViewById<TextView>(R.id.title_text_dialog).text =
requireContext.getString(R.string.help_notification)
findViewById<TextView>(R.id.content_text_dialog).text =
requireContext.getString(R.string.notification_content)
}
lockoutDialog = AlertDialog.Builder(
requireContext(),
R.style.CustomAlertDialog
)
.setView(view)
.setOnDismissListener { lockoutDialog = null }
.create()
.also { it.show() }
}