4

I am following a tutorial out of a book. I know how anceint right? Well anyways I have arrived to a point where we are making a simple application that allows for users to input text and press a button. When the button is pressed the text that is inputted is converted into all CAPS.

There is a step where we add a feature to the app so that when the user presses enter (after the desired text has been inputted) the text is converted. (No need to press the convert button)

I have ran my application and tried to debug it, but have gotten no where. From what logcat is saying (or more like not saying) onKey is never called.

Is there something that I am missing? Below is the java class and the layout for the Activity. Thanks in advance.

TipCal.java (don't ask why I named it that)

package com.njh.add.tipclaculator;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class TipCal extends Activity implements OnClickListener, OnKeyListener{

    private TextView tv = null;
    private Button convertButton = null;
    private Button quitButton = null;
    private EditText et = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tip_cal);
        tv = (TextView)findViewById(R.id.my_TextView);
        convertButton = (Button)findViewById(R.id.my_Button);
        quitButton = (Button)findViewById(R.id.quit);
        et = (EditText)findViewById(R.id.my_EditText);

        convertButton.setOnClickListener(this);
        quitButton.setOnClickListener(this);
        et.setOnKeyListener(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.tip_cal, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onClick(View v) {
        Log.d(v.toString(),"In onClick");
        if(v == convertButton){
            //Take the text from the EditText and make it all CAPS
            //Then update the TextView with the CAPS string
            String editTextContent = et.getText().toString();
            editTextContent = editTextContent.toUpperCase();
            tv.setText(editTextContent);
            et.setText("");
        }else if(v == quitButton){
            this.finish();
        }
    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        //When the user press enter after typing in what they want to
        //convert the conversion is done. No need to press any other button

        Log.d(v.toString(),"In onKey");

        if(event.getAction() == KeyEvent.ACTION_DOWN){
            if(event.getKeyCode() == KeyEvent.KEYCODE_ENTER){
                this.onClick(v); //Not sure this is legal but don't see why I couldn't do this.
            }
        }

        return false;
    }
}

Here is the layout XML for this Activity

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:id="@+id/base"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.njh.add.tipclaculator.TipCal" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="My First Android Application"
        android:id="@+id/my_TextView" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Touch Me"
            android:id="@+id/my_Button" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Quit"
            android:id="@+id/quit" />
    </LinearLayout>

    <EditText 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="Type something to get turned into CAPS"
        android:id="@+id/my_EditText" />
</LinearLayout>

UPDATE

What I have found so far is that TextWatcher is actually hitting my code. Which is good. Now my app crashes when I hit enter.

BUT!

I will take that. I think the new issue is the three functions that you implement when you make a TextWatcher are called twice for each key you press. (on keydown and keyup). This is just a guess. Anyone know for sure?

UPDATE TextWatcher was not calling its methods multiple times per change. The soft keyboard was updating things like automatically adding spaces or autocorrecting. I noticed this when I inputted "ll" and my keyboard updated it to "lol " which would cause it to update for the change from "ll" to "lol" to "lol ".

I am about to do a bit more digging. If I conclude on a workable solution I will post to share the wealth.

Noah Herron
  • 630
  • 4
  • 23
  • are you typing with the software keyboard? e.g on the screen? – Ben Aug 15 '15 at 20:17
  • Yes. I just found a post that might answer my question. Was there a point in time where onKey worked for both soft and hardware KBs? – Noah Herron Aug 15 '15 at 20:18
  • 1
    i've no idea, i just know that i had some issues with the software keyboard once, let me know if it works with the hardware. – Ben Aug 15 '15 at 20:19
  • 1
    You should also take a look at [TextWatcher](http://developer.android.com/reference/android/text/TextWatcher.html). An example on how to use it is given [here](http://stackoverflow.com/a/8699607/451951). – Antrromet Aug 15 '15 at 20:22
  • Hi! Did you solve the problem? TextWatcher is primitive and cannot overcome some difficulties that I can in OnKeyListener. – CoolMind Jan 31 '19 at 07:59

1 Answers1

2

OnKeyListener "This is only useful for hardware keyboards". That is a direct quote from View.OnKeyListener.

@Antrromet you suggested check out this post, and I did. It got me started.

I then ran into index out of bounds exceptions. Which using Android Studio helped out greatly with.

I have came to a solution for using TextWatcher to call other functions. Below is my Java file for my fix. There were no issues in my XML as I could see. You my notice that I have changed the name and things that go along with it like the package. This is because I used Android Studio for this go and not Eclipse. Since I use Intellij at work, and Android Studio is built by the same people, I found it much easier to debug and such with AS.

Since I rewrote this some of the variable names have changed. You should still be able to follow this code. It is set up the same as in the OP.

package com.example.developer.demo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.util.Locale;

//NOTE I now implement TextWatch not OnKeyListener
//the AppCompatActivity was changed because ActionBarActivity is deprecated
//look it up on StackOverflow I'll post the link at the bottom of this answer
public class Demo extends AppCompatActivity implements OnClickListener, TextWatcher {

    private TextView textView = null;
    private Button convertButton = null;
    private Button quitButton = null;
    private EditText editText = null;

    private int before = 0;
    private int on = 0;
    private int after = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);

        textView = (TextView)findViewById(R.id.app_info_tv);
        convertButton = (Button)findViewById(R.id.convert_but);
        quitButton = (Button)findViewById(R.id.quit_but);
        editText = (EditText)findViewById(R.id.main_et);

        convertButton.setOnClickListener(this);
        quitButton.setOnClickListener(this);
        editText.addTextChangedListener(this); //use this instead of OnKeyListener

        Log.d("Demo","in onCreate");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        //the same as in OP
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        //the same as in OP
    }


    @Override
    public void onClick(View v) {
        Log.d("Demo","in onClick");

        //See what View was clicked
        if(v == convertButton) {
            //turn the inputted sting into all Caps
            String textToConvert = editText.getText().toString();
            textToConvert = textToConvert.toUpperCase(Locale.getDefault());
            textView.setText(textToConvert);
            editText.setText("");
        }else if(v == quitButton){
            this.finish();
        }
    }

    //Instead on onKey with OnKeyListener you get three functions with
    //TextWatcher. Like their names suggest one is called before changes,
    //one during, and one after. 

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        Log.d("****","***************");
        Log.d("Demo","in BTC: "+String.valueOf(before++));
        Log.d("Demo","\ts: "+s.toString());
        Log.d("Demo","\tstart: "+String.valueOf(start));
        Log.d("Demo","\tcount: "+String.valueOf(count));
        Log.d("Demo","\tafter: "+String.valueOf(after));
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        Log.d("Demo","in OTC: "+String.valueOf(on++));
        Log.d("Demo","\ts: "+s.toString());
        Log.d("Demo","\tstart: "+String.valueOf(start));
        Log.d("Demo","\tcount: "+String.valueOf(count));
        Log.d("Demo","\tbefore: "+String.valueOf(before));
    }

    @Override
    public void afterTextChanged(Editable s) {
        Log.d("Demo","in ATC: "+after++);
        Log.d("Demo","\ts: "+s.toString());
        String textToConvert = s.toString();
        if(textToConvert.length() > 0 && Character.compare(textToConvert.charAt(textToConvert.length()-1),'\n') == 0){
            onClick(convertButton);
        }
    }
}

If anyone is curious about the debug log just let me know I will post it. It helped me with seeing how I was getting my exception.

So the best part...What did I do?

In my app I wanted to "bind" the onClick function to the "Enter" button on the soft keyboard as well as the convert Button object in the layout.

I realized that my onClick function was messing with the EditText object that the TextWatcher was on. So I had to figure out a way to tell when onClick was called by the TextWatcher or by the OnClickListener.

The "Enter" button will put a "\n" (newline) at the end of the string. So I would check for that. If it was there I would call onClick.

Then onClick would set the text in the EditText to the empty string "". Changing the text caused TextWatcher to go through it's three methods again. When it got the the afterTextChange it would index int a String that was empty and KABOOM. (because I was trying to grab the last character which is length - 1)

String empty = "";
int length = empty.length(); // 0
empty.charAt(length - 1); //if empty had three thing in it the length - 1 would be 2 (the index of the last character in the String)
//BUT it doesn't so empty[-1] is bad

^^^No Bueno^^^

So I added a check for String length greater than 0. Now I could rely that I would not index into an empty string.

I have tested my App a good bit calling onClick in anyway and in any order. It all is working well.

The link that I was talking about in the code for why I used AppCompactActivity is here. It pretty much makes it a ActionBarActivity even though that class is now deprecated.

Community
  • 1
  • 1
Noah Herron
  • 630
  • 4
  • 23