2

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>
Broski-AC
  • 739
  • 6
  • 12
  • Clicking in a certain area of the screen will loose the focus of the first EditText.. In this instance, it's the EditText of the multichoice activity – Broski-AC Nov 18 '21 at 23:05
  • I've got 2 duplicate DecorViews in the V8 layout inspector, and only 1 FrameLayout for my V10 layout inspector – Broski-AC Nov 18 '21 at 23:30

0 Answers0