Text change should be detected after - android

In a AutoCompleteTextView I want to show the auto complete list after there is a pause of 1 sec by user in typing.
I tried using handler
handler.postDelayed( new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
if ( text.length() >= 3 ) {
// do something
} else if ( text.length() == 0 ) {
// do something
}
}
}, 1000 );
This code is a part of onTextChanged. So what it is happening is whenever there is a text change at that moment postDelayed is called and inside code will be called after a second. So how can I prevent that so the inner code is only called when there is a puase of 1 sec by user in typing.
e.g:
If I type Ind (pause of 1 sec) then inner code execute should happen.
But I type India then inner code should not execute for Ind, Indi, India.
Need your suggestions.

Create two static variables holding timestamps.
lastTimeStamp and currentTimeStamp, then you can do something like this:
#Override
public void run() {
if (currentTimeStamp - lastTimeStamp > 1000) {
// TODO Auto-generated method stub
if ( text.length() >= 3 ) {
// do something
} else if ( text.length() == 0 ) {
// do something
}
}
}
New approach:
At the beginning of your onTextChanged method, put a current TimeStamp in a class-variable.
Thereafter create an AsyncTask in the onTextChanged, which is just doing a
Thread.sleep(1000) in the doInBackground-method.
Then you make an if-statement in the postExcute method, checking if the difference between TimeStamp in the class-variable and the current TimeStamp, if this is larger than 1000 post your handler.

I just stood at the same problem.
I am calling Googles Geocode suggestion API onTextChanged() and that caused many issues.
Users typing would cause masses of API requests, most of them not required and that uses up the API (costs money) and reduces the experience the user receives from the app.
I considered the solutions here but I did not like any of them in the end, they seemed unclean or like a hack.
I did not want to make further calls into the UI to get the current value of the View (again) or check system times.
The original question actually already solves the problem, it just causes multiple runnables to be alive when a user types.
So the question should be: "How to remove my anonymous runnable before it causes trouble ?"
All you have to do it use the original approach (the delayed call using the anonymous runnable) and whenever the onTextChanged function is called you REMOVE any previous runnables.
You can also extend the code and check if the value really changed (like using global previous value String) so the runnable is not killed if the user entered a char and removed it again.
So all you need to do:
1. Make the Handler a variable of the class instead of creating it inside the onTextChanged.
2. At the begin of onTextChanged remove the runnables from the handler
Here is a fully functional example:
Handler onchangeHandler = new Handler();
String last_onTextChanged=""; // may speed up some cases of user input, can be removed
#Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3)
{
final String value = charSequence.toString();
if (value.length() < 3) {autoCompleteAdapter.clear();return;} // threshold of 3 chars
if (!last_onTextChanged.equals(value))
onchangeHandler.removeCallbacksAndMessages(null);
else
return;
last_onTextChanged=value;
onchangeHandler.postDelayed(new Runnable()
{
#Override
public void run()
{
// RUN CODE HERE, fill your autoCompleteAdapter etc.
}
},800);
}

There are 2 options:
Use Timer and invoke cancel() on previous tasks when you are going to run the next one.
Extend Runnable and pass in a string for this autocompletion. If this string is not equal to current string in view (that means user changed the string), then you should not display autocompletion.
Code example:
abstract class MyRunnable {
private String str;
public MyRunnable(String str) {
this.str = str;
}
}
...
handler.postDelayed(new MyRunnable(currentViewStr) {
#Override
public void run() {
if (currentViewStr.equals(str)) {
// show autocompletion
}
}
}, 1000);

you can also cancel the previous Runnable msg in message queue first in onTextChanged()

Related

Android Handler updates TextView only with last setText()

The following code is from Head First Android. It is for a stopwatch app.
I have a few questions in the following code:
The code runs like -> OnCreate -> runTimer() (skips handler.post()) -> OnStart -> onResume -> Comes back to handler.post().
Why does it skip hander.post() in the first place?
I have two textView.setText(). But the first one doesn't work. It's always the last one. I put the second one just to see what the code does after postDelay() method.
Why doesn't the first one work? I am expecting the text to jump back and forth from "hello" to "hh:mm:ss".
So what exactly happens during the 1-second delay after postdelay() is executed.
Does the code starts running normally and when its 1 second the postDelay() is called?
why is this used in postDealy(this, 100). shouldn't it be this.run()?
public class MainActivity extends AppCompatActivity {
private boolean running = false;
private int counter = 0;
private Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
runTimer();
}
public void onClickStart(View view){
running = true;
}
public void runTimer(){
final TextView textView = findViewById(R.id.timer);
handler.post(new Runnable() {
#Override
public void run() {
int hours = counter/3600;
int minutes = (counter%3600)/60;
int secs = counter%60;
String time = String.format("%d:%02d:%02d", hours, minutes, secs);
textView.setText(time); // Doesn't set it to this - see last line
if(running){
counter++;
}
handler.postDelayed(this,1000); // what does happens between next one second
textView.setText("hell0"); // Always set it to this
}
});
}
Why does it skip hander.post() in the first place?
It is not skipped, it will be executed after onResume() returns. All the Runnables, queued though a Handler associated with the main thread, start their execution only after onResume() returns.
Why doesn't the first one work?
It does work. You just can't visually see it because the two method calls, textView.setText(), are invoked "almost" at the same time.
The following sequence of calls happen at each run():
textView.setText(time),
the same Runnable is posted to the queue with handler.postDelayed(this,1000). Immediately after that
textView.setText("hell0") is called
Why doesn't the first one work? I am expecting the text to jump back and forth from "hello" to "hh:mm:ss".
You should implement an extra logic to switch between time and "hell0" at each run() execution.
E.g. create a boolean flag in the Activity and set either time or "hell0" depending on the flag value (don't forget to change the flag value at each run() execution).
why is this used in postDelay(this, 100). shouldn't it be this.run()?
No, this.run() is executed synchronously (and immediately) and is of type void. The code won't compile as postDelay() expects the Runnable type, not void.
handler.postDelayed(this,1000);
This used to run your function after 1 second. It is a delay for 1 second.
The code written in your handler will execute after a second. That's all.

Android Handler/Timer Request

So I have a question, and if it's a stupid one I do apologize up front, I have tried to search for it but not sure what to search for exactly. I am trying to run a delayed task, but only if my int = 0, would this work correctly like I am wanting it to?
public static void runTask(String p)
{
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run()
{
pendingRequest = pendingRequest - 1;
if (pendingRequest == 0)
{
context.startActivity(p);
}
}
}, 4000);
}
}
What I want it to do is only run if pendingRequest is 0, but I have other activities that add to pending request after the runTask() is called. If this doesn't make any sense please let me know and I will try to reword it.
This is a bit of an obscure way to do things so seeing just this snippet I cant tell exactly what the desired behavior is, however, it should work if you make the parameter "p" final. Im also not familiar with a startActivity method that takes a string instead of an intent, but I cant tell if "context" is actually an Android Context object, but I'm assuming it is.
What I'm not sure about is why you would wait 4 seconds BEFORE decrementing pendingRequest. I would think you want to decrement, allow 4 seconds for someone else to add a pending request, and if it's still 0 after the wait start the Activity... but, again, I cant tell from the snippet.
Try this:
private static Object requestLock = new Object();
public static void runTask(final String p)
{
synchronized(requestLock)
{
if (--pendingRequest > 0) // Decrement first
{
// There are more requests
return;
}
}
// Wait 4 sec and if there are still no requests start the activity.
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run()
{
synchronized(requestLock)
{
if (pendingRequest == 0)
{
context.startActivity(p);
}
}
}
}, 4000);
}
}
Note: You will also need to add a synchronized block where you increment the pendingRequests.

How to use android handler in a loop

I am building my first android application and I am trying to make a memory game. Anyhow, I need to make an array of buttons change color for 1 second and then return to its original color in order, for example: button1 changes to yellow, stays like that for 1 second then returns to gray, then button2 changes to yellow for 1 second then returns, and so on. I tried using the handler but it always works only after the last iteration, this is my code:
for (i = 0; i < 9; i++) {
buttonList.get(i).setBackgroundColor(Color.YELLOW);
runnable =new Runnable(){
#Override
public void run() {
buttonList.get(i).setBackgroundColor(Color.GRAY);
}
};
handler.postDelayed(runnable,1000);}
what am I doing wrong?
EDIT
Found How to do it. First I need to make a runnable class that takes paramaters ex MyRunnable implements Runnable (using Runnable interface), then writing a method that uses this paramater, I can't do it with the regular one because it depends on i and i changes with the iteration.
You need to create a new Runnable inside each loop because all 9 delayed posts are running the same runnable that you create on the 9th and final loop since the loop no doubt takes less than a second to complete. So try something like this:
for (i = 0; i < 9; i++) {
buttonList.get(i).setBackgroundColor(Color.YELLOW);
Runnable runnable = new Runnable(){
#Override
public void run() {
buttonList.get(i).setBackgroundColor(Color.GRAY);
}};
handler.postDelayed(runnable,1000);
}
You're synchronously (at the same time) setting all buttons' colors to yellow, and also creating 9 asynchronous tasks (one for each button) to change color to gray after one second. It means all buttons will change colors back to gray after around 1 second, (more or less) at the same time.
Think of the handler as a queue that you add tasks to. The call postDelayed() is scheduling your tasks to be executed in the future, but all of them are scheduled at the same time, so all of them will be executed at the same time in the future.
I haven't run it, but I think this approach is more of what you are looking for:
// Those are fields
private int buttonIndex = 0;
private boolean yellow = false;
private final Handler handler = new Handler(new Handler.Callback() {
#Override
public void handleMessage(Message msg) {
if (!yellow) {
buttonList.get(buttonIndex).setBackgroundColor(Color.YELLOW);
handler.sendEmptyMessageDelayed(0, 1000);
} else {
buttonList.get(buttonIndex).setBackgroundColor(Color.GRAY);
if (++buttonIndex < 9) handler.sendEmptyMessage(0);
}
yellow = !yellow;
}});
// Call this to start the sequence.
handler.sendEmptyMessage(0);
Note that I'm using sendEmptyMessage*() instead of post*(), but either approach could be used. Additionally, handler's messages (tasks) can have input parameters, so it'd be nice to use them.

Android - Can not change GUI from a thread

long time watcher, first time writer :P
I got this problem:
I can't seem to change anything that has to do with the layout of android from my playSoundThread.
In this example, I use EventListeners. I already tried the simple way. I passed the ScrollView through, so that the thread can change it. But when it's happening, the thread stops immediately. And even when I use EventListeners, the same Problem occurs.
Changing a variable and posting log information works fine, but not layout Objects.
The first thing is, that I want to scroll a HorizontalScrollView from out the Thread's run() method.
the second case is, that, if the thread comes to it's end, I wanna fire an "i'm finished"-Event and change the image and function of an ImageButton
Here's the run()-method of the thread
public void run() {
if(this.playbackPosition < rhythm.tracks.get(0).sounds.size()) {
for (Track t : rhythm.tracks) {
if (t.sounds.get(this.playbackPosition).equals("1")) {
this.sp.play(t.SoundID, 1, 1, 1, 0, 1);
}
}
this.playbackPosition++;
if ( this.playbackPosition >= (this.scrollIndex*(192/this.zoom)) ){
this.scrollIndex++;
//Here I wanna fire the "Scroll" event
for(ScrollListener sl : scrollListeners){
sl.update(scrollPositions[scrollIndex]);
}
}
}
//This is the point where the playback is finished and the event to change a button is fired
else {
tmpListener.update();
}
}
}
The declaration of the OnPlaybackFinishedListener can be found in the class Player, which is the parent of the PlaySoundThread:
public void addOnPlaybackFinishedListener(){
tmpListener = new OnPlaybackFinishedListener() {
#Override
public void update() {
scheduledExecutorService.shutdown();
//this is a seconds Listener, which was implemented to test, if the problem still occurs with a little listener chain
shutdownListener.update();
}
};
}
public void addShutdownListener(OnExecutorShutdown sl){
this.shutdownListener = sl;
}
And here's the part of the MainActivity which is the parent class of Player and adds the shutdown listener and the ScrollListener:
awesomePlayer.addScrollListener(new ScrollListener(){
public void update(int position){
Log.i("ScrollListener update()","Running ScrollTo( "+position+", "+VIEW_rhythmscroll.getScrollY()+")");
VIEW_rhythmscroll.scrollTo(position, VIEW_rhythmscroll.getScrollY());
}
});
awesomePlayer.addOnPlaybackFinishedListener();
awesomePlayer.addShutdownListener(new OnExecutorShutdown() {
#Override
public void update() {
// TODO Auto-generated method stub
//This method changes the Pause Button to a Play Button with a new OnClickListener and a new Picture
BUTTON_STOP.performClick();
}
});
Can anyone help? Is there another way to avoid this problem? I'm developing on Android 2.2
Is it even possible to access UI elements from a thread?
Thanks in advance :)
You can't modify UI elements from a seperate thread, UI elements have to be modified from the main, UI Thread. There are a lot of topics on this, but you can update the UI by using an AsyncTask's onPostExecute(), onPreExecute(), or onProgressUpdate() methods, the Activity class's runOnUiThread(Runnable action), or by sending a Message to a Handler.

Android Chronometer, retain time state (and keep counting in background)

I have a timer that counts up from the time a user encounters that activity
I am currently using a Chronometer set during onCreate (initially started only when certain conditions are met). But I need the chronometer to keep counting upward until the app and all its views are closed (I have an "Exit" function to do that).
The problem is that the Chronometer gets reset to zero on every time I look at another tab and come back to its activity. (This has to do with the oncreate, but I dont know the way around it)
I didn't find an intuitive way to save the chronometer's state or countup in the background on its own (or to perhaps keep track of the time on my own and update the chronometer visually at a different point in time)
One idea I had was to start the Chronometer with a service and let the service keep counting , while having a textview in the existing activity update using the chronometer's current time tally as a string
any insight on a known approach to this problem be appreciated!
This is further complicated because this is an activity in a tabhost, and tabhosts call both onPause and onResume every time you load a view, so this breaks lifecycle functions.
There are a number of ways to persist the time. The easiest one I have found is to store the time in the Intent that was used to create the original activity via getIntent().putExtra("START_TIME", floatvalue). You may retrieve the value with getIntent().getFloatExtra("START_TIME", 0f). Doing it this way has a number of benefits:
It doesn't break the Activity LifeCycle and does not require a Context.
It can be passed easily between other Activities and Applicaitons.
It persists among Pauses and Stops.
It doesn't require special listeners.
It doesn't create any new objects (the Intent is the one used to create the Activity the first time).
This solution is great for persisting in a Tabbed Activity, or across Dialogs, etc. It has some limitations if leaving the Application to a more memory intensive one, but only if your Activity is destroyed (due to memory).
Because of my Tabhost, the lifecycle functions could not be relied on.
What I did was make the chronometer a static global in a central class, and added a ontabchangedlistener within my tabhost that checked to see if the tab being changed to was the tab with the chronometer. If this was true then it stores the Long value of the chronometer's current time.
tabHost.setOnTabChangedListener(new OnTabChangeListener(){
#Override
public void onTabChanged(String arg0) {
// TODO Auto-generated method stub
if(arg0.contentEquals("homeGroup"))
{
//store time in centralhelper.java
//stopWatch is of type Chronometer
//stopWatchLastTime is of type Long and is initially set to zero. Chronometer uses milliseconds to determine time, will never be zero after set
CentralHelper.stopWatchLastTime = CentralHelper.stopWatch.getBase();
}
}
});
When my homeGroup view loads, the onResume() function is called, there is a condition here to retrieve the time for the chronometer to resume counting from. Despite the fact that a tabhost will call both onPause() and onResume() in EVERY load outside of normal lifecycle functions, they still get called before onCreate()
public void onResume(){
super.onResume();
//update Chronometer with time stored in tabchangelistener
if(CentralHelper.stopWatchLastTime!=0)
CentralHelper.stopWatch.setBase(CentralHelper.stopWatchLastTime);
}
this allowed me to do a similar check in onCreate()
if(CentralHelper.stopWatchLastTime!=0)
{
CentralHelper.stopWatch.start(); //this is where it resumes counting from the base set in onResume()
}
else
{
CentralHelper.stopWatch.start();
CentralHelper.stopWatch.setBase(SystemClock.elapsedRealtime());
}
When you switch to a different activity the previous one is paused (onPause, asand so on, in attached image) when you came back to the activity it is resumed, but occasionaly when dalvik runs out of memory your Activity object can be deleted when ton showing.
If you keep your application data in the Activity instance you might loose it accidentally, please read this Activity Lifecycle http://developer.android.com/reference/android/app/Activity.html
This approach is tested and it works really well.
Try this:
Take a boolean volatile variable which will control your thread(start/stop). Take three text views, hour, min and sec text views, and remove chronometer completely. Update your UI using a Handler Write the following code.
public void timeUpdate()
{
timerThread = new Thread(new Runnable() {
#Override
public void run() {
while(continueThread){
Date newDate = new Date();
if(((newDate.getTime()) - date.getTime()) > 1000){
secondCounter = secondCounter+1;
mHandlerUpdateSec.post(mUpdateSec);
System.out.println("Inside the Theread ..."+secondCounter);
if(secondCounter > 59){
minuteCounter = minuteCounter + 1;
mHandlerUpdateMinute.post(mUpdateMinute);
secondCounter = 0;
if(minuteCounter > 59){
hourCounter = hourCounter + 1;
mHandlerUpdateHour.post(mUpdateHour);
minuteCounter = 0;
}
}
}
try{
timerThread.sleep(1000);
}catch (Exception e) {
// TODO: handle exception
}
}
}
});
timerThread.start();
}
The continueThread is a boolean volatile variable. Setting it to false will stop the thread. The timerThread is an instance of thread. There are three counters, hour, min and sec counters which will give you the latest time values. The handlers are updated as follows.
final Handler mHandlerUpdateSec = new Handler();
final Runnable mUpdateSec = new Runnable() {
public void run() {
String temp = "" + secondCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
secTextView.setText("0" + secondCounter);
else
secTextView.setText("" + secondCounter);
}
};
final Handler mHandlerUpdateMinute = new Handler();
final Runnable mUpdateMinute= new Runnable() {
public void run() {
String temp = "" + minuteCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
minTextView.setText("0" + minuteCounter);
else
minTextView.setText("" + minuteCounter);
}
};
final Handler mHandlerUpdateHour = new Handler();
final Runnable mUpdateHour = new Runnable() {
public void run() {
String temp = "" + hourCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
hourTextView.setText("0" + hourCounter);
else
hourTextView.setText("" + hourCounter);
}
};
Now, whenever you want to start the timer, set continueThread to true and call timeUpdate(). To stop it, just do continueThread = false. To start the thread again, set continueThread to true and call timeUpdate() again. Make sure you update the counters accordingly while you start/stop the timer.
You could save the start time in a sharedpreferences (or file, etc.) and establish your count-up from that (rather than starting at 0) in onResume().
Your UI may need some changes to handle the fact that you will have to reset the start time, since it could theoretically count forever.

Categories

Resources