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.