I've got an issue with an EditText view automatically receiving focus not on activity launch, but once another EditText within the activity has been edited. And once that first EditText has focus, it won't let go. The extremely odd thing is that I have only seen this behavior when I am testing on Android v8 (API 26). On v10 (API 29), my program works just fine.
I've tried basically everything except the invisible overlay that's described here, and again - it all works fine in later Android versions. I'm just noticing this behavior in v8. If anyone has an idea of how to fix this - or if this is a consequence of old tech, I'd appreciate you letting me know!
Activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activity_project_question_root"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:focusableInTouchMode="true"
android:clickable="true"
android:focusable="true"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" >
<ImageView
android:id="@+id/close_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="@dimen/margin_2"
android:layout_marginTop="@dimen/margin_2"
android:src="@drawable/ic_close" />
<TextView
android:id="@+id/header_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_three_fourths"
style="@style/H2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="" />
<ImageView
android:id="@+id/delete_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="@dimen/margin_2"
android:layout_marginEnd="@dimen/margin_2"
android:src="@drawable/ic_delete"
android:visibility="invisible"
tools:visibility="visible"/>
<View
android:id="@+id/toolbar_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height_dp"
android:layout_marginTop="@dimen/margin_1"
android:background="@color/ccc_gray"
app:layout_constraintTop_toBottomOf="@id/close_btn" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/nsv"
android:layout_width="match_parent"
android:layout_height="@dimen/zero_dp"
app:layout_constraintTop_toBottomOf="@id/toolbar_container"
app:layout_constraintBottom_toTopOf="@id/save_btn_divider"
android:layout_marginHorizontal="@dimen/margin_3"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Headline.32"
android:text="@string/header"
app:layout_constraintTop_toBottomOf="@idtoolbar_container"
app:layout_constraintStart_toStartOf="parent"
android:layout_margin="@dimen/margin_2"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/answer_questions_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_1_5"
android:padding="@dimen/padding"
android:background="@drawable/gray_rounded_rectangle"
app:layout_constraintTop_toBottomOf="@id/header">
<TextView
android:id="@+id/answer_questions_header"
android:layout_width="@dimen/zero_dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/light_bulb_iv"
android:layout_marginEnd="@dimen/margin_1"
android:padding="@dimen/padding_half"
style="@style/Body.16"
android:text="@string/warning" />
<ImageView
android:id="@+id/light_bulb_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_light_bulb_icon" />
</androidx.constraintlayout.widget.ConstraintLayout>
// EditTexts are part of items within the RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/qa_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_2"
android:paddingBottom="@dimen/padding_one_fourth"
android:orientation="vertical"
android:scrollbars="vertical"
android:scrollbarStyle="outsideInset"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/questions_container"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
// File continues on but nothing should be relevant
Activity.kt file
@AndroidEntryPoint
class Activity : SMBaseActivity(BottomNavigationView2020.State.HIDE_NAVBAR), ProjectQuestionsContract.View {
@Inject
lateinit var presenter: Contract.Presenter
lateinit var BookingProjectDetailsAdapter: Adapter
private var saveChangesDialog: PopupView? = null
private var fragment: QADropdownChooserFragment = QADropdownChooserFragment()
private var editable: Boolean = false
private var isSaveActive: Boolean = false
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
val BookingDetailsViewObject = intent.getParcelableExtra<BookingDetailsViewObject>(BOOKING_DETAILS_VIEW_OBJECT_KEY)
?: throw NullPointerException("")
close_btn.setOnClickListener { presenter.onClosePressed(isSaveActive) }
save_btn.isEnabled = false
save_btn.setOnClickListener {
presenter.onSavePressed()
}
editable = intent.getBooleanExtra(IS_EDITABLE_KEY, false)
if (editable){
delete_btn.setOnClickListener { presenter.onDeletePressed() }
delete_btn.visibility = View.VISIBLE
}
showQuestions(BookingDetailsViewObject, false)
presenter.onViewCreated(this, BookingDetailsViewObject)
}
override fun onStart(){
super.onStart()
presenter.onStart()
}
override fun getContentViewLayoutId(): Int {
return R.layout.activity_project_questions
}
override fun showLoading(show: Boolean){
showLoadingDialog(R.string.loading)
}
override fun stopLoadingSpinner(stayOnPage: Boolean) {
stopLoadingSpinner(save_btn, stayOnPage)
}
override fun startLoadingSpinner() {
startLoadingSpinner(save_btn)
}
override fun showNetworkError(throwable: Throwable, tryAgain: (() -> Unit)?, dismiss: (() -> Unit)?, serverErrorTextResId: Int, clientErrorTextResId: Int): Boolean {
return handleNetworkError(throwable, tryAgain, dismiss, getString(serverErrorTextResId), getString(clientErrorTextResId))
}
override fun showQuestions(BookingDetailsViewObject: BookingDetailsViewObject, loading: Boolean) {
BookingProjectDetailsAdapter = ProjectQuestionsAdapter(BookingDetailsViewObject.getFilteredQuestionsAndAnswers(), loading, customerQACallbacks, context, ManagerFactory.getInstance().schedulerProvider)
qa_rv.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = BookingProjectDetailsAdapter
isNestedScrollingEnabled = false
}
}
override fun closeActivityAfterSaving() {
overridePendingTransition(R.anim.slide_from_bottom, R.anim.slide_out_bottom)
setResult(Activity.RESULT_OK)
finish()
}
override fun closeActivityAfterDeleting() {
overridePendingTransition(R.anim.slide_from_bottom, R.anim.slide_out_bottom)
setResult(Activity.RESULT_OK, Intent().putExtra(DELETE_FLAG, true ))
finish()
}
override fun closeActivityWithoutSaving(){
overridePendingTransition(R.anim.slide_from_bottom, R.anim.slide_out_bottom)
setResult(Activity.RESULT_CANCELED)
finish()
}
override fun showSaveChangesModal() {
saveChangesDialog = PopupView.showViewAsPopupWithBlurBackground(this.window.decorView, R.layout.dialog_save_changes_to_project, closeOnBackgroundClick = false, viewInflatedListener = { view ->
val headerText: TextView = view.findViewById(R.id.dialog_save_changes_to_project_header)
headerText.text = resources.getString(R.string.save_changes)
view.findViewById<Button>(R.id.dialog_save_button).setOnClickListener {
presenter.onSaveModalSavePressed()
}
view.findViewById<Button>(R.id.dialog_dont_save_button).setOnClickListener {
presenter.onSaveModalDoNotSavePressed()
}
view.findViewById<TextView>(R.id.dialog_cancel_tv).setOnClickListener {
presenter.onSaveModalCancelPressed()
}
}, useFadeTransition = true)
}
override fun dismissSaveModal() {
saveChangesDialog?.popupWindow?.dismiss()
saveChangesDialog = null
}
override fun displayDeleteModal() {
showHAAlert(
getString(R.string.delete_modal),
"",
getString(R.string.button_delete),
getString(R.string.cancel_button),
{ presenter.onQADeletePressed() },
{ presenter.onQACancelPressed() },
false
)
}
override fun dismissDeleteModal() {
super.unblockUi()
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
val v = currentFocus
if (v is EditText) {
val outRect = Rect()
v.getGlobalVisibleRect(outRect)
if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
v.clearFocus()
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.getWindowToken(), 0)
}
}
}
return super.dispatchTouchEvent(event)
}
override fun enableSave(answersHaveChanged: Boolean) {
if (answersHaveChanged){
save_btn.isActivated = true
save_btn.isEnabled = true
isSaveActive = true
} else {
save_btn.isActivated = false
save_btn.isEnabled = false
isSaveActive = false
}
}
private val customerQACallbacks = object : Adapter.CustomerQAClickListener {
override fun onTextQuestionResponseChanged(question: BookingSurveyQuestionAndResponseDto?, textBoxText: String) {
presenter.onTextQuestionResponseChanged(question, textBoxText)
}
override fun onMultiOptionsUpdated(question: BookingSurveyQuestionAndResponseDto?, selectedAnswersList: MutableList<Int?>) {
presenter.onMultiSelectionChanged(question, selectedAnswersList)
}
}
companion object {
const val BOOKING_DETAILS_VIEW_OBJECT_KEY = "BOOKING_KEY"
const val IS_EDITABLE_KEY = "IS_EDITABLE"
const val DELETE_FLAG = "DELETE_FLAG"
@JvmStatic
fun newIntent(context: Context, BookingDetailsViewObject: BookingDetailsViewObject, editable: Boolean) : Intent {
return Intent(context, ProjectQuestionsActivity::class.java).apply {
putExtra(BOOKING_DETAILS_VIEW_OBJECT_KEY, BookingDetailsViewObject)
putExtra(IS_EDITABLE_KEY, editable)
}
}
}
}
RV Adapter
class Adapter(
val projectQuestions: List<BookingSurveyQuestionAndResponseDto>?,
val loading: Boolean,
val callbacks: CustomerQAClickListener,
val context: Context,
val schedulerProvider: SchedulerProvider
) : RecyclerView.Adapter<Adapter.BookingProjectDetailsViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BookingProjectDetailsViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
TEXT_AREA_SUBTYPE -> {
val view = inflater.inflate(R.layout.fpp_customer_qa_text_box, parent, false)
TextAreaQAViewHolder(view)
}
MULTI_CHOICE_SUBTYPE -> {
val view = inflater.inflate(R.layout.fpp_customer_qa_multi_select, parent, false)
MultiChoiceQAViewHolder(view)
}
else -> {
val view = inflater.inflate(R.layout.fpp_customer_qa_text_box, parent, false)
TextAreaQAViewHolder(view)
}
}
}
override fun onBindViewHolder(holder: BookingProjectDetailsViewHolder, position: Int) {
val question = projectQuestions?.getOrNull(position)
val isLast = (position == projectQuestions?.lastIndex)
holder.bindQuestion(question, position, isLast)
}
override fun getItemCount(): Int {
return projectQuestions?.size ?: 0
}
override fun getItemViewType(position: Int): Int {
val current = projectQuestions?.getOrNull(position)
return when (current?.getQuestionSubtype()) {
QuestionSubType.TEXT_AREA -> TEXT_AREA_SUBTYPE
QuestionSubType.MULTI_CHOICE -> MULTI_CHOICE_SUBTYPE
else -> TEXT_AREA_SUBTYPE
}
}
companion object {
private const val TEXT_AREA_SUBTYPE = 1
private const val MULTI_CHOICE_SUBTYPE = 2
private const val PLEASE_SELECT = "Please select"
}
abstract class BookingProjectDetailsViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
private val questionLabel: TextView = itemView.findViewById(R.id.question_label)
private val questionNumber: TextView = itemView.findViewById(R.id.question_number)
fun bindQuestion(question: BookingSurveyQuestionAndResponseDto?, position: Int, isLast: Boolean) {
questionLabel.text = question?.question?.label
questionNumber.text = (position + 1).toString().plus(".")
if (isLast) {
itemView.findViewById<View>(R.id.question_divider).visibility = View.GONE
}
bindQuestionTypeSpecificViews(question)
}
abstract fun bindQuestionTypeSpecificViews(question: BookingSurveyQuestionAndResponseDto?)
}
inner class TextAreaQAViewHolder(view: View) :
Adapter.BookingProjectDetailsViewHolder(view) {
override fun bindQuestionTypeSpecificViews(question: BookingSurveyQuestionAndResponseDto?) {
val textBox = itemView.findViewById<EditText>(R.id.free_response_question_comment)
val textResponse = question?.response?.text
if (textResponse.isNullOrEmpty()) {
textBox.setText("")
} else {
textBox.setText(textResponse)
textBox.isActivated = true
}
textBox.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
callbacks.onTextQuestionResponseChanged(question, textBox.text.toString())
}
}
}
}
inner class MultiChoiceQAViewHolder(view: View) :
Adapter.BookingProjectDetailsViewHolder(view) {
lateinit var selectedAnswersHolder: MultiChoiceQAAnswerHolder
lateinit var otherTextBox: EditText
override fun bindQuestionTypeSpecificViews(question: BookingSurveyQuestionAndResponseDto?) {
selectedAnswersHolder = MultiChoiceQAAnswerHolder(question)
otherTextBox = itemView.findViewById(R.id.multi_select_free_response_question_comment)
question?.response?.text?.let {
otherTextBox.setText(it)
otherTextBox.isActivated = true
}
val MultiSelectAdapter = MultiSelectAdapter(question, context, ::textBoxEnabledCallback, ::multiSelectQuestionCallback)
val multiSelectRV = itemView.findViewById<RecyclerView>(R.id.multi_select_rv)
multiSelectRV.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL,false)
adapter = ProjectQuestionsMultiSelectAdapter
isNestedScrollingEnabled = false
}
}
private fun multiSelectQuestionCallback(id: Int?, addToList: Boolean) {
if (addToList){
selectedAnswersHolder.setSelectedAnswers(id)
} else {
selectedAnswersHolder.removeSelectedAnswers(id)
}
}
private fun textBoxEnabledCallback(question: BookingSurveyQuestionAndResponseDto?) {
otherTextBox.visibility = View.VISIBLE
otherTextBox.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
callbacks.onTextQuestionResponseChanged(question, otherTextBox.text.toString())
}
}
}
}
inner class MultiChoiceQAAnswerHolder(question: BookingSurveyQuestionAndResponseDto?) {
val question: BookingSurveyQuestionAndResponseDto? = question
private var selectedAnswersList = question?.response?.optionIds ?: mutableListOf()
fun setSelectedAnswers(selectedAnswer: Int?) {
selectedAnswersList.add(selectedAnswer)
callbacks.onMultiOptionsUpdated(question, selectedAnswersList)
}
fun removeSelectedAnswers(selectedAnswer: Int?){
selectedAnswersList.remove(selectedAnswer)
callbacks.onMultiOptionsUpdated(question, selectedAnswersList)
}
fun getSelectedAnswers(): MutableList<Int?>{
return selectedAnswersList
}
}
interface CustomerQAClickListener {
fun onQADropdownQuestionClicked(question: BookingSurveyQuestionAndResponseDto?)
fun onTextQuestionResponseChanged(question: BookingSurveyQuestionAndResponseDto?, textBoxText: String)
fun onMultiOptionsUpdated(question: BookingSurveyQuestionAndResponseDto?, selectedAnswersList: MutableList<Int?>) }
}
I'm including just one of the layout files containing the EditView, but if you need more, let me know. fpp_customer_qa_multi_select.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="@dimen/margin_1_5">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/multi_select_question_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin"
android:layout_marginEnd="@dimen/margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/question_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/question_label"
tools:text="4."
style="@style/Headline.16"/>
<TextView
android:id="@+id/question_label"
android:layout_width="@dimen/zero_dp"
android:layout_height="wrap_content"
android:breakStrategy="simple"
android:layout_marginStart="@dimen/margin_three_fourths"
app:layout_constraintStart_toEndOf="@id/question_number"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="Why don't you tell me about the job that you are choosing to pursue"
style="@style/Headline.16"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/multi_select_reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_2"
android:layout_marginTop="@dimen/margin_2"
android:layout_marginEnd="@dimen/margin_2"
android:text="@string/project_questions_multi_select"
android:textAppearance="@style/Label2MiddleGray"
app:layout_constraintTop_toBottomOf="@id/multi_select_question_header"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/multi_select_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
android:layout_marginEnd="@dimen/margin_2"
android:paddingTop="@dimen/padding_15dp"
android:paddingRight="@dimen/padding_1_5"
android:paddingBottom="@dimen/padding_15dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/multi_select_reminder" />
// The section that's causing problems
<controls.EditTextWithKeyboardEvents
android:id="@+id/multi_select_free_response_question_comment"
style="@style/Label1"
android:layout_width="match_parent"
android:layout_height="144dp"
android:layout_marginTop="@dimen/margin_2"
android:layout_marginEnd="@dimen/margin_2"
android:background="@drawable/fpp_text_area_background"
android:gravity="top"
android:inputType="textAutoComplete|textCapSentences|textMultiLine|textAutoCorrect"
android:paddingLeft="@dimen/padding_1_5"
android:paddingTop="@dimen/padding_15dp"
android:paddingRight="@dimen/padding_1_5"
android:paddingBottom="@dimen/padding_15dp"
android:textCursorDrawable="@drawable/blue_cursor"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/multi_select_rv" />
<View
android:id="@+id/question_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height_dp"
android:layout_marginTop="@dimen/margin_1"
android:background="@color/ccc_gray"
app:layout_constraintTop_toBottomOf="@id/free_response_question_comment" />
</LinearLayout>