The purpose of my app is: User enters a number and clicks a button. The button uses the input to calculate the Fibonacci sequence with a timer - with each number in the sequence displaying each second to a textView. But when I try to run the timer I get the CalledFromWrongThreadException. I've posted my code below. As you can tell by my log statements I believe I know which line is causing the problem. I think it's because I'm calling a method which is outside my onclicklistener but when I move that other method around I just cause more problems.
I've read a couple other posts and I'm not really sure what the proper way is to print to a text area using my method. Does anyone know how I can make this work?
public class MainActivity extends Activity {
// primary widgets
private EditText editText;
private TextView textView;
private Button button1;
static int seconds = 0;
static Timer timer;
static ArrayList<Integer> fibList = new ArrayList<Integer>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.editText1);
textView = (TextView) findViewById(R.id.textView2);
button1 = (Button) findViewById(R.id.button1);
final int delay = 1000;
final int period = 1000;
timer = new Timer();
//Attempt to clear TextView
textView.setText("");
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//Clear Textview
String array = " ";
fibList.clear();
textView.setText(array);
//Log.i("ARRAY", "ATTEMPT to CLEAR"+fibList);
String input = editText.getText().toString();
int number = Integer.parseInt(input);
int tmp = 0;
// confirm input
if (number < 20) {
Toast.makeText(getApplicationContext(),
"You entered: " + number, Toast.LENGTH_LONG).show();
for (int i = 0; i <= number; i++) {
fibList.add(fib(i));
// sum even numbers
if (fib(i) % 2 == 0) {
tmp += fib(i);
}
}
} else {
Toast.makeText(getApplicationContext(),
"Number is too Large: " + number, Toast.LENGTH_LONG)
.show();
}
//I believe error occurs in this method
Log.i("TEST", "START TIMER");
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
Log.i("TEST", "RUN TIMER");
int nextIndex = setInterval();
Log.i("TEST", "SET INTERVAL");
if (nextIndex < fibList.size()) {
Log.i("TEST", "TRY TO PRINT");
//It looks like error occurs here when I try to print to textView
textView.setText(fibList.get(nextIndex)+ " ");
Log.i("TEST", "NEXT INDEX"+fibList.get(nextIndex));
Log.i("TEST", "DID PRINT");
}
}
}, delay, period);
Log.i("TEST", "END TIMER");
}
});
}
// run fibonacci sequence
public static int fib(int n) {
if (n < 2) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}
//counts up for every element through the array
public static final int setInterval() {
if (seconds >= fibList.size())
timer.cancel();
return seconds++;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
You can use
runOnUiThread(new Runnable(){
public void run(){
textView.setText("aaa");
}
});
Have your timer post a message to a Handler. The handler will, by default, run on the main thread. IT can then change the UI as needed, so just put the body of your timer into that handler.
I just had this problem and came on to StackOverflow to check out a solution. Didn't find anything proper but a little more experimenting with lot many Logs and debugging got me my solution.
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText("Your String");
}
});
In my code, the problem was due to me accessing Two TextViews in seperate threads one after another using TextView.post(new Runnable...). I guess this was due to it not being able to access UI Thread (as it was busy with previous thread changes). Setting both TextViews together in UI Thread solved the problem. So posting here for anyone else who might be perplexed by similar problem. Hope it helps.
Related
i am new to app development and i am currently trying to make a counter app.
i have set up the buttons so that the text changes with the button pressed.
although, i am trying to make the text display the adding or subtracting value and then show the total number afterwards.
For example, my counter starts at 20;
if i press the add button i want the textview text to change to +1, +2, +3 and so on for however many times the button is pressed within a time of 2 seconds. After 2 second i want the textview to show the total again. So if i pressed it 3 times, after 2 seconds on the 3rd press i want it to display "23" and not "+3" anymore.
Then, if i pressed the minus button just once the textview will display "-1" and then after 2 seconds change to "22", etc. I also want the text color to change to green when the add button is pressed, red when the minus is pressed and white when displaying the total or at default.
Here is my code so far, i am probably off by a mile but i would really like some help to be shown in the right direction.
public class MainActivity extends Activity {
private Thread timer;
TextView lifepointsP1;
ImageButton p1AddL, p1SubL;
int counter1;
int count;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
counter1 = 20;
count = 0;
lifepointsP1 = (TextView) findViewById(R.id.tvP1LP);
p1AddL = (ImageButton) findViewById(R.id.bP1add);
p1SubL = (ImageButton) findViewById(R.id.bP1take);
//the timer
timer = new Thread() {
#Override
public void run(){
try {
synchronized(this){
wait(2000);
}
} catch(InterruptedException e){
e.printStackTrace();
}
count = 0;
lifepointsP1.setTextColor(Color.argb(0, 0, 0, 0));
}
};
p1AddL.setOnClickListener(new View.OnClickListener() {
//the buttons
public void onClick(View v) {
counter1++;
count++;
if (count == 0) {
if (counter1 >= 9|counter1 <= -1) {
lifepointsP1.setText("" + counter1);
} else {
lifepointsP1.setText("0" + counter1);
}
} else if (count > 0) {
lifepointsP1.setText("+" + count);
lifepointsP1.setTextColor(Color.argb(0, 0, 188, 0));
}
}
});
p1SubL.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
counter1--;
count--;
if (count == 0) {
if (counter1 >= 9|counter1 <= -1) {
lifepointsP1.setText("" + counter1);
} else {
lifepointsP1.setText("0" + counter1);
}
} else if (count < 0) {
lifepointsP1.setText("-" + count);
lifepointsP1.setTextColor(Color.argb(0, 214, 0, 0));
}
}
});
}
//the counter reset if pressed
#Override
public boolean onTouchEvent(MotionEvent evt) {
if(evt.getAction() == MotionEvent.ACTION_DOWN) {
synchronized(timer){
timer.start();
}
}
return true;
}
}
Ive also set it up so that if the button is pressed while the timer is counting it will reset the count to start again.
Thanks in advanced for any help, its really appreciated!
What you need is a CountdownTimer
You can implement your logic in the onFinish() method then.
You should go with Handler class
A Handler allows you to send and process Message and Runnable objects
associated with a thread's MessageQueue. Each Handler instance is
associated with a single thread and that thread's message queue. When
you create a new Handler, it is bound to the thread / message queue of
the thread that is creating it -- from that point on, it will deliver
messages and runnables to that message queue and execute them as they
come out of the message queue.
There are two main uses for a Handler: (1) to schedule messages and
runnables to be executed as some point in the future; and (2) to
enqueue an action to be performed on a different thread than your own.
public int i = 0;
Then:
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if(i != 20) {
text.append(" " + i);
i++;
handler.postDelayed(this, 1000);
} else {
handler.removeCallbacks(this);
text.append(" Stopped");
}
}
}, 1000);
}
In an android app I intend to allow users to answer a random sum then a new one appears on screen. This is repeated 10 times and then a final score will then be given. However I am unsure how to update the sum so that after each each a new random is shown on screen.
Below is my current code:
public class Test extends Activity {
//declare vars
TextView text;
EditText answer;
Button submit;
int random1;
int random2;
String question;
int correctAnswer;#
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
// initialising variables
initialiseVars();
//set up random
setUpRandom();
//Set text view equal to question
text.setText(question);
//updateQuestion?
}
public void initialiseVars() {
text = (TextView) findViewById(R.id.tvTopRandomTest);
answer = (EditText) findViewById(R.id.etEnterAnswerRandomTest);
submit = (Button) findViewById(R.id.btnSubmitRandomTest);
}
public void setUpRandom() {
//setting up randoms
Random random = new Random();
// Generating random number between 1 and 12
random1 = random.nextInt(12) + 1;
// Generating another random number between 1 and 12
random2 = random.nextInt(12) + 1;
question = random1 + " x " + random2 + " = ";
correctAnswer = random1 * random2;
}
public void updateQuestion() {
//CODE TO UPDATE QUESTION
}
}
Add Button ClickListener so that when user press submit button it will update question and clean all previous values
submit = (Button) findViewById(R.id.btnSubmitRandomTest);
submit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
updateQuestion();
}
}
Maintain a count in your activity and increase it in updateQuestion
public void updateQuestion() {
if (Int.parseString(answer.getText().toString()) != correctAnswer) {
// Show toast or something
return;
}
tries++;
if (tries == 10) return; // or do something else;
answer.setText("");
setUpRandom();
text.setText(question); // add this line in your setUpRandom();
}
To generate random integers look at this. Hopefully this will help you out.
I did this code and want to make a traffic light on and off several times and then stop and choose one of the two lights at random, but the program does the operation, but does not show the changing lights as the operation does
private ImageView redLight;
private ImageView greenLight;
private Button Button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.traffic_lights);
redLight = (ImageView) findViewById(R.id.red_light);
greenLight = (ImageView) findViewById(R.id.green_light);
Button = (Button) findViewById(R.id.start_button);
// Assign click listeners to buttons
Button.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (v == Button) {
int flag = 0;
for (int i = 0; i <= 100; i++) {
if (flag == 0) {
turnOnRedLight();
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
Log.i("Traffic", "Red");
flag = 1;
} else {
turnOnGreenLight();
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
Log.i("Traffic", "Green");
flag = 0;
}
}
}
}
private void turnOnRedLight() {
redLight.setImageResource(R.drawable.red_on);
}
private void turnOnGreenLight() {
greenLight.setImageResource(R.drawable.green_on);
}
}
You are only sleeping for 100ms so you probably aren't seeing the lights change. Change that to something like 1000 or maybe even 500 and you will probably see the change
Thread.sleep(1000);
Also, a couple notes
Although with this code it may not be a problem, sleeping on the UI Thread usually isn't a good idea. Use a Thread and update UI with runOnUiThread(), use AsyncTask, or Handler.
Don't name your Button "Button". Name it something that isn't an Android keyword like button, btn, startButton, startBtn, or something similar.
When distinguishing between which Button was clicked use the Button id instead of the Object. So you would want to change it to
#Override
public void onClick(View v) {
if (v.getId() == (R.id.start_button)) {
you could also use a switch statement here but not important.
I'm a little new to Android. Working on an app that let's user input a number and then calculates the Fibonacci Sequence and then displays each element in the sequence at 1 second intervals. But I have an issue when I try to display using a thread inside a for loop. I try print from an array list but it gives me an error with my counter variable (j). I tried running this same exact method in a much simpler app and the array list worked just fine. I don't know why it doesn't work this time. I hope this is a simple obvious error. Can anyone tell me why? Code is posted below:
public class MainActivity extends Activity {
// primary widgets
private EditText editText;
private TextView textView;
private Button button1;
Thread thread;
static ArrayList<Integer> fibList = new ArrayList<Integer>();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.editText1);
textView = (TextView) findViewById(R.id.textView2);
button1 = (Button) findViewById(R.id.button1);
//Attempt to clear TextView
textView.setText("");
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//Clear Textview
String array = " ";
fibList.clear();
textView.setText(array);
final String input = editText.getText().toString();
int number = Integer.parseInt(input);
int tmp = 0;
// confirm input
if (number < 20) {
Toast.makeText(getApplicationContext(),
"You entered: " + number, Toast.LENGTH_LONG).show();
for (int i = 0; i <= number; i++) {
fibList.add(fib(i));
// sum even numbers
if (fib(i) % 2 == 0) {
tmp += fib(i);
}
}
} else {
Toast.makeText(getApplicationContext(),
"Number is too Large: " + number, Toast.LENGTH_LONG)
.show();
}
Log.i("TEST", "ARRAY"+fibList);
thread = new Thread(new Runnable(){
#Override
public void run(){
for(int j = 0; j < fibList.size(); j++){
runOnUiThread(new Runnable(){
#Override
public void run(){
//ERROR OCCURS HERE: Cannot refer to a non-final variable j
//inside an inner class defined in a different method
textView.append(fibList.get(j).toString());
textView.append("");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //wait one second
}
}
});
thread.start();
}
});
}
// run fibonacci sequence
public static int fib(int n) {
if (n < 2) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
a simple workaround to solve your issue is to declare a temp final variable in this way:
for(int j = 0; j < fibList.size(); j++){
final int finalJ = j;
runOnUiThread(new Runnable(){
#Override
public void run(){
//ERROR OCCURS HERE: Cannot refer to a non-final variable j
//inside an inner class defined in a different method
textView.append(fibList.get(finalJ).toString());
textView.append("");
}
});
You have a more serious problem.
You are attempting to manipulate a texView from a thread. That's not permitted. Only the GUI thread can manipulate screen objects or else Bad Things Happen.
Take a look at AsyncTask. It is designed to address exactly the problem you are trying to solve.
http://developer.android.com/reference/android/os/AsyncTask.html
I hope this isn't a stupid question. I'm having some trouble clearing a TextView. I've looked around and everyone keeps saying use: textView.setText(""); in onCreate but doesn't seem to work for some reason. Basically, my app just accepts a number from an editText then runs the Fibonacci sequence (when a button is clicked) and displays the result in a textView. Well, the sequence displays fine but I want the textview to clear every time I click the button - so far it just keeps adding more text to what's already there.
Am I placing textView.setText(""); in the wrong location? Or am I just missing some other concept? (I also tried placing it from my OnClick - that didn't work either).
Here is my code:
public class MainActivity extends Activity {
// primary widgets
private EditText editText;
private TextView textView;
private Button button1;
static ArrayList<Integer> fibList = new ArrayList<Integer>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.editText1);
textView = (TextView) findViewById(R.id.textView2);
button1 = (Button) findViewById(R.id.button1);
//Attempt to clear TextView
textView.setText("");
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
String input = editText.getText().toString();
int number = Integer.parseInt(input);
int tmp = 0;
// confirm input
if (number < 20) {
Toast.makeText(getApplicationContext(),
"You entered: " + number, Toast.LENGTH_LONG).show();
for (int i = 0; i <= number; i++) {
fibList.add(fib(i));
// sum even numbers
if (fib(i) % 2 == 0) {
tmp += fib(i);
}
}
} else {
Toast.makeText(getApplicationContext(),
"Number is too Large: " + number, Toast.LENGTH_LONG)
.show();
}
String array = fibList.toString();
textView.setText(array);
}
});
}
// run fibonacci sequence
public static int fib(int n) {
if (n < 2) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
If you want the TextView to clear on each button click then the .setText must go in you onClick. The reason you would put the .setText in your onCreate is to clear the text as soon as your activity is created, but you do not have anything to clear just yet since your button has not yet been pushed so setText will do nothing. Also, since your onCreate will only run once for your activity, it will never go back to the setText again. Try the following:
public class MainActivity extends Activity {
// primary widgets
private EditText editText;
private TextView textView;
private Button button1;
static ArrayList<Integer> fibList = new ArrayList<Integer>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.editText1);
textView = (TextView) findViewById(R.id.textView2);
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
textView.setText(""); //Clear the TextView
fibList.clear(); //Clear your array list before adding new elements
String input = editText.getText().toString();
int number = Integer.parseInt(input);
int tmp = 0;
// confirm input
if (number < 20) {
Toast.makeText(getApplicationContext(),
"You entered: " + number, Toast.LENGTH_LONG).show();
for (int i = 0; i <= number; i++) {
fibList.add(fib(i));
// sum even numbers
if (fib(i) % 2 == 0) {
tmp += fib(i);
}
}
} else {
Toast.makeText(getApplicationContext(),
"Number is too Large: " + number, Toast.LENGTH_LONG)
.show();
}
String array = fibList.toString();
textView.setText(array);
}
});
}
// run fibonacci sequence
public static int fib(int n) {
if (n < 2) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
you will need to clear textView on Button click event before adding new results to it.do it as:
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
textView.setText(""); //<<<<<< clear TextView on Button Click
.....
The problem is more likely in fibList that is not being cleared