Home > Mobile >  Android Runtime Error when inputting numbers into a EditText box
Android Runtime Error when inputting numbers into a EditText box

Time:01-10

I am creating a simple calories to kilojoules calculator that should provide a conversion as a user enters a number into one of the two EditText boxes (similar to the type of converter you would find here. I used chat.openai.com for the majority of the code found within MainActivity.kt.

However, whenever I enter a number, the app will freeze and immediately crash due to a runtime error. Is someone able to help troubleshoot why the textWatcher could be causing these crashes?

Logcat Error Log:

                    at com.example.energyconverter.MainActivity$onCreate$textWatcher$1.onTextChanged(MainActivity.kt:41)
                    at android.widget.TextView.sendOnTextChanged(TextView.java:10789)
                    at android.widget.TextView.setText(TextView.java:6401)
                    at android.widget.TextView.setText(TextView.java:6227)
                    at android.widget.EditText.setText(EditText.java:121)
                    at android.widget.TextView.setText(TextView.java:6179)
                    at com.example.energyconverter.MainActivity$onCreate$textWatcher$1.onTextChanged(MainActivity.kt:47)
                    at android.widget.TextView.sendOnTextChanged(TextView.java:10789)
                    at android.widget.TextView.setText(TextView.java:6401)
                    at android.widget.TextView.setText(TextView.java:6227)
                    at android.widget.EditText.setText(EditText.java:121)
                    at android.widget.TextView.setText(TextView.java:6179)
                    at com.example.energyconverter.MainActivity$onCreate$textWatcher$1.onTextChanged(MainActivity.kt:41)
                    at android.widget.TextView.sendOnTextChanged(TextView.java:10789)
                    at android.widget.TextView.handleTextChanged(TextView.java:10904)
                    at android.widget.TextView$ChangeWatcher.onTextChanged(TextView.java:13807)
                    at android.text.SpannableStringBuilder.sendTextChanged(SpannableStringBuilder.java:1268)
                    at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:577)
                    at androidx.emoji2.text.SpannableBuilder.replace(SpannableBuilder.java:315)
                    at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:508)
                    at androidx.emoji2.text.SpannableBuilder.replace(SpannableBuilder.java:305)
                    at androidx.emoji2.text.SpannableBuilder.replace(SpannableBuilder.java:49)
                    at android.text.method.NumberKeyListener.onKeyDown(NumberKeyListener.java:129)
                    at android.widget.TextView.doKeyDown(TextView.java:8557)
                    at android.widget.TextView.onKeyDown(TextView.java:8331)
                    at android.view.KeyEvent.dispatch(KeyEvent.java:2854)
                    at android.view.View.dispatchKeyEvent(View.java:14478)
                    at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1964)
                    at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1964)
                    at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1964)
                    at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1964)
                    at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1964)
                    at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1964)
09:42:09.998  E     at com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:490)
                    at com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1880)
                    at android.app.Activity.dispatchKeyEvent(Activity.java:4156)
                    at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.java:124)
                    at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:86)
                    at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.java:142)
                    at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:601)
                    at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:60)
                    at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:3106)
                    at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:404)
                    at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:6278)
                    at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6144)
                    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5626)
                    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5683)
                    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5649)
                    at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5814)
                    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5657)
                    at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5871)
                    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5630)
                    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5683)
                    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5649)
                    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5657)
                    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5630)
                    at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8562)
                    at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8513)
                    at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:8482)
                    at android.view.ViewRootImpl$ViewRootHandler.handleMessageImpl(ViewRootImpl.java:5391)
                    at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:5263)
                    at android.os.Handler.dispatchMessage(Handler.java:106)
                    at android.os.Looper.loopOnce(Looper.java:201)
                    at android.os.Looper.loop(Looper.java:288)
                    at android.app.ActivityThread.main(ActivityThread.java:7839)
                    at java.lang.reflect.Method.invoke(Native Method)
                    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
                    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

MainActivity.kt:

package com.example.energyconverter

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText

class MainActivity : AppCompatActivity() {
    // Define constants for the conversion factor between kilojoules and calories
    private val KJ_TO_CAL = 4.184
    private val CAL_TO_KJ = 1.0 / KJ_TO_CAL

    // Define the EditText widgets for the kilojoules and calories
    private lateinit var kilojoulesText: EditText
    private lateinit var caloriesText: EditText

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        kilojoulesText = findViewById(R.id.kilojoules_text)
        caloriesText = findViewById(R.id.calories_text)

        // Set up a TextWatcher to update the conversion when the user types in either text box
        val textWatcher = object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                // Do nothing
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                // Do nothing
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                // Convert the value in the other text box when the user types in either one
                if (s == kilojoulesText.text) {
                    val kj = s.toString().toDoubleOrNull()
                    if (kj != null) {
                        val cal = kj * KJ_TO_CAL
                        caloriesText.setText(cal.toString())
                    }
                } else if (s == caloriesText.text) {
                    val cal = s.toString().toDoubleOrNull()
                    if (cal != null) {
                        val kj = cal * CAL_TO_KJ
                        kilojoulesText.setText(kj.toString())
                    }
                }
            }
        }

        // Attach the TextWatcher to both EditText widgets
        kilojoulesText.addTextChangedListener(textWatcher)
        caloriesText.addTextChangedListener(textWatcher)
    }
}

activity_main.xml (Go easy me for the alignment on these EditText boxes as I've just thrown them onto the screen for now.)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@ id/calories_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="100dp"
        android:layout_marginTop="154dp"
        android:layout_marginEnd="101dp"
        android:ems="10"
        android:hint="@string/text_calories_hint"
        android:importantForAutofill="no"
        android:inputType="number"
        android:textColorHint="#757575"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="parent"
        app:layout_constraintVertical_bias="1.0"
        tools:ignore="TouchTargetSizeCheck,TouchTargetSizeCheck" />

    <EditText
        android:id="@ id/kilojoules_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="99dp"
        android:layout_marginTop="180dp"
        android:layout_marginEnd="102dp"
        android:layout_marginBottom="484dp"
        android:ems="10"
        android:hint="@string/text_kilojoules_hint"
        android:importantForAutofill="no"
        android:inputType="number"
        android:minHeight="48dp"
        android:textColorHint="#757575"
        app:layout_constraintBottom_toTopOf="@ id/calories_text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

As I am quite new to Kotlin and Android Studio, The most I have been able to do is utilise LOGCAT to understand where the problem stems from. LOGCAT points to both lines below:

                        caloriesText.setText(cal.toString())
                        kilojoulesText.setText(kj.toString())

CodePudding user response:

class SomeActivity : AppCompatActivity() {
// Define constants for the conversion factor between kilojoules and calories
private val KJ_TO_CAL = 4.184
private val CAL_TO_KJ = 1.0 / KJ_TO_CAL

// Define the EditText widgets for the kilojoules and calories
private lateinit var kilojoulesText: EditText
private lateinit var caloriesText: EditText

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_some)

    kilojoulesText = findViewById(R.id.kilojoules_text)
    caloriesText = findViewById(R.id.calories_text)

    // Set up a TextWatcher to update the conversion when the user types in either text box
    val textWatcher = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            // Do nothing
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            // Do nothing
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            if (caloriesText.isFocused) {
                if (s == caloriesText.text) {
                    val kj = s.toString().toDoubleOrNull()
                    if (kj != null) {
                        val cal = kj * KJ_TO_CAL
                        kilojoulesText.setText(cal.toString())
                    }
                }
            }

            if (kilojoulesText.isFocused) {
                if (s == kilojoulesText.text) {
                    val cal = s.toString().toDoubleOrNull()
                    if (cal != null) {
                        val kj = cal * CAL_TO_KJ
                        caloriesText.setText(kj.toString())
                    }
                }
            }

        }
    }

    // Attach the TextWatcher to both EditText widgets
    kilojoulesText.addTextChangedListener(textWatcher)
    caloriesText.addTextChangedListener(textWatcher)
}
}

Hi @JTek, can you please try this code and let me know if it works as expected. I made a minor change here. I added a check to see which EditText user is making changes to by checking the focus state of it. This will help us prevent infinite loop of callbacks.

In simple words, if user is trying to calculate KJ, then we should only listen to calories EditText changes and vice versa.

  • Related