Scenario:
I ran into a strange issue while testing out threads in my fragment.
I have a fragment written in Kotlin with the following snippet in onResume():
override fun onResume() {
super.onResume()
val handlerThread = HandlerThread("Stuff")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
Thread.sleep(2000)
tv_name.setText("Something something : " + isMainThread())
}
}
is MainThread() is a function that checks if the current thread is the main thread like so:
private fun isMainThread(): Boolean = Looper.myLooper() == Looper.getMainLooper()
I am seeing my TextView get updated after 2 seconds with the text "Something something : false"
Seeing false tells me that this thread is currently not the UI/Main thread.
I thought this was strange so I created the same fragment but written in Java instead with the following snippet from onResume():
#Override
public void onResume() {
super.onResume();
HandlerThread handlerThread = new HandlerThread("stuff");
handlerThread.start();
new Handler(handlerThread.getLooper()).post(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText("Something something...");
}
});
}
The app crashes with the following exception as expected:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7313)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1161)
I did some research but I couldn't really find something that explains this. Also, please assume that my views are all inflated correctly.
Question:
Why does my app not crash when I modify my TextView in the runnable that's running off my UI thread in the Fragment written in Kotlin?
If there's something in some documentation somewhere that explains this, can someone please refer me to this?
I am not actually trying to modify my UI off the UI thread, I am just curious why this is happening.
Please let me know if you guys need any more information. Thanks a lot!
Update:
As per what #Hong Duan mentioned, requestLayout() was not getting called. This has nothing to do with Kotlin/Java but with the TextView itself.
I goofed and didn't realize that the TextView in my Kotlin fragment has a layout_width of "match_parent." Whereas the TextView in my Java fragment has a layout_width of "wrap_content."
TLDR: User error + requestLayout(), where thread checking doesn't always occur.
The CalledFromWrongThreadException only throws when necessary, but not always. In your cases, it throws when the ViewRootImpl.checkThread() is called during ViewRootImpl.requestLayout(), here is the code from ViewRootImpl.java:
#Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
And for TextView, it's not always necessary to relayout when we update it's text, we can see the logic in the source code:
/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
*/
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.
int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
*/
makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {
autoSizeText();
invalidate();
return; // return with out relayout
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return; // return with out relayout
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}
As you can see, in some cases, the requestLayout() is not called, so the main thread check is not introduced.
So I think the key point is not about Kotlin or Java, it's about the TextViews' layout params which determined whether requestLayout() is called or not.
Most likely, in Kotlin case, there is some overhead in setText() which assures that it runs in UI thread.
Related
I'm developing simple drawing application and I was wondering if it is possible to add visible pointer when touching canvas? What I mean is something similar to mouse indicator on PC. The reason for that is - when using eraser it is not visible where exactly your touch is registered and with the pointer, it would help (especially when using finger touch, because the pen is quite precise). I've tried with other similar topics but none of them answered the question.
taken from this pos :
How to programmatically enable "show touches" option in Android?
• enable show touches:
Settings.System.putInt(context.getContentResolver(),
"show_touches", 1);
• To disable show touches:
Settings.System.putInt(context.getContentResolver(),
"show_touches", 0);
Remember to add android.permission.WRITE_SETTINGS to your Manifest.
EDIT:
here is a DrawView.java that implements View.OnTouchListener and defines multiple draw modes. however it lacks the indicators.
in main activity inside setListener for the DrawView you override onStartDrawing method: here you can decide whether or not to show an indicator and how to style it depending on DrawView's current mode ( pen ,eraser etc ) and on the other interface methodes move the indicator if its visible and hide it when drawing ends. check the code:
mDrawView.setOnDrawViewListener(new DrawView.OnDrawViewListener() {
#Override public void onStartDrawing() {
canUndoRedo();
// decide whether or not to show an
// indicator and style it depending on
// mDrawView's attributes and current drawing mode
setIndicator(mDrawView.getDrawingMode(),mDrawView.getDrawWidth());
}
#Override
public void onMove(MotionEvent motionEvent) {
if(isIndicatorVisible){
indicatorView.animate()
.x(motionEvent.getX())
.y(motionEvent.getY())
.setDuration(0)
.start();
}
}
#Override public void onEndDrawing() {
canUndoRedo();
//hide indicator here
if (isIndicatorVisible){
indicatorView.setVisibility(View.GONE);
isIndicatorVisible = false;
}
}
here is the edits that i add to DrawView.java :
defined new interface method void onMove(MotionEvent motionEvent)
public interface OnDrawViewListener {
void onStartDrawing();
void onEndDrawing();
void onClearDrawing();
void onRequestText();
void onMove(MotionEvent motionEvent); // added this method
}
called the new method inside onToch:
case MotionEvent.ACTION_MOVE:
onDrawViewListener.onMove(motionEvent); // added this line
mDrawMoveHistory.get(mDrawMoveHistory.size() - 1).setEndX(motionEvent.getX()).setEndY(motionEvent.getY());
if (mDrawingTool == DrawingTool.PEN || mDrawingMode == DrawingMode.ERASER) {
mDrawMoveHistory.get(mDrawMoveHistory.size() - 1).getDrawingPathList()
.get(mDrawMoveHistory.get(mDrawMoveHistory.size() - 1).getDrawingPathList().size() - 1)
.lineTo(motionEvent.getX(), motionEvent.getY());
}
invalidate();
break;
here's setIndicator method :
private void setIndicator(DrawingMode drawingMode, int drawWidth) {
if (drawingMode == DrawingMode.ERASER){
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(drawWidth, drawWidth);
indicatorView.setLayoutParams(params);
GradientDrawable border = new GradientDrawable();
border.setStroke(1, 0xFF000000); //black border with full opacity
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
indicatorView.setBackgroundDrawable(border);
} else {
indicatorView.setBackground(border);
}
indicatorView.setVisibility(View.VISIBLE);
isIndicatorVisible = true;
}
}
(Android) On a music player, you update the seekbar as expected with this:
PRECISION_SEEKBAR = 100000;
((SeekBar) findViewById(R.id.seekBar2)).setMax(PRECISION_SEEKBAR);
timerSeekBarUpdate.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
final SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar);
#Override
public void run() {
if (control == null || player == null) {
cancel();
return;
}
seekBar.setProgress((int) (player.getCurrentPosition() * PRECISION_SEEKBAR / player.getDuration()));
...
However, if the focus is on the seek bar, talkback steadily and nonstop gives feedback for the progress. Like "seek control 25%", "seek control 25%", "seek control 25%", "seek control 26%", "seek control 26%", "seek control 27%"
I'm missing sth but couldnot solve the problem. I have set the contentDescription to other than #null. But then it reads the content description this time without stopping.
On Spotify client, I checked, it reads the progress as "xx percent" just once. Despite saving the focus on the seekbar.
When I edit the precision for 1 or 100, then you lose the precision on the seekbar. It looks like there are a few parts in the song. You either play one or another by swiping on the seekbar.
Has anybody experienced sth like this? I couldn't find anything on google docs, stack network or somewhere else.
You can just override sendAccessibilityEvent() so it ignores description updates:
#Override
public void sendAccessibilityEvent(int eventType) {
if (eventType != AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION) {
super.sendAccessibilityEvent(eventType);
}
}
As Altoyyr mentioned, this has the side effect of ignore ALL description updates, including scrolling with volume buttons. So you'll need add back sending the event for volume press actions:
#Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
super.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
}
}
return super.performAccessibilityAction(action, arguments);
}
I had the problem and found that SeekBar reads the percentage on every update.
It helped, that I update the SeekBar only when the percentage changes but still keep a high precision (in my case in ms).
#Override
public void updateSeekBar(final int currentPosInMillis, final int durationInMillis) {
long progressPercent = calculatePercent(currentPosInMillis, durationInMillis);
if (progressPercent != previousProgressPercent) {
seekBar.setMax(durationInMillis);
seekBar.setProgress(currentPosInMillis);
}
previousProgressPercent = progressPercent;
}
private int calculatePercent(int currentPosInMillis, int durationInMillis) {
if(durationInMillis == 0) {
return 0;
}
return (int) (((float)currentPosInMillis / durationInMillis) * 100);
}
previousProgressPercent is initialized to -1.
Please note that this solution is not the same as Spotify does it.
Spotify overrides the message announced by the system when the SeekBar gets selected.
This has following 2 effects:
Updates can be made as often as you want without the percentage beeing repeated
When the percentage changes while the SeekBar is selected then nothing gets announced
Point 2 might me a drawback depending on what you want to achieve.
On a specific view I am adding a global layout listener:
myView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// code
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.someView);
if(linearLayout != null && linearLayout.getVisibility() == View.VISIBLE) {
linearLayout.setVisibility(View.GONE);
}
LinearLayout otherLayout = (LinearLayout) findViewById(R.id.someOtherView);
otherLayout.setVisibility(View.GONE);
//other code
}
});
In some cases but I don't know exactly how, during rotation some times it happens that there is NPE for the line otherLayout.setVisibility(GONE)
To be honest I am not sure why the code checks for null in the lines above for the linearLayout and not for the otherLayout but both are defined in the same resource file and are not e.g. removed programmatically anywhere.
The only difference is that the otherLayout is not visible.
So my question is: Are there any things I should look out for on rotation with global layout listeners? Why am I getting NPE in some random cases?
Update:
Both views are part of the same xml file. And actually one defined is after the other. The only difference is that someView is defined as visible and otherView as not visible. Having said that though, there can be such a case where someView is already visible/rendered while otherView has not been yet rendered/made visible when the rotation is happening depending on the current width
I suspect there is a difference between the layouts of findViewById(R.id.someView) and findViewById(R.id.someOtherView). The difference is the timing, for sure, and possibly the layout xml file that it is inflating. With R.id.someOtherView, it is done immediately while R.id.someView, it is executed when the layout is drawn OR at any other time as in screen orientation change since the width/height of the screen changed.
NEW:
final LinearLayout linearLayout = (LinearLayout)findViewById(R.id.someView);
myView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//LinearLayout linearLayout = (LinearLayout)findViewById(R.id.someView);
if(linearLayout != null && linearLayout.getVisibility() == View.VISIBLE) {
linearLayout.setVisibility(View.GONE);
}
...
}
});
Notes:
I commented out the findViewById() inside onGlobalLayout(), basically removing it.
I think it's not safe to call findViewById() inside the listener since layouts cannot be cached, similar to views and anything related to UI objects. This is what I meant above on my last sentence. I know it's not obvious. I think this explains that the issue is not consistent or strange, as you said essentially.
On my app I have an ImageView which I turned into a Bitmap for editing. I need to detect which pixels on the ImageView were touched by the user. In addition, if the user draws a line with his finger, I need to know all the pixels that were touched in order to change them. How do I detect which pixels were touched?
Ok Jonah, here are some directions for you.
I guess you want that blending effect to react quickly to user input so first thing you'd better go for a custom SurfaceView instead of a ImageView because it is more suitable for drawing high frame rate animations required in 2D action games and animations. I strongly recommend you to read this guide; giving special attention to the part about the use of SurfaceView, before going any further. You will basically need to create a class that extends SurfaceView and implements SurfaceHolder.Callback. This view will then be responsible to listen for user touch events and to render the frames to animate the blending effect.
Take a look at following code as a reference:
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
public MainView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this); // Register this view as a surface change listener
setFocusable(true); // make sure we get key events
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen...
case MotionEvent.ACTION_CANCEL:
// User dragged his finger out of the view bounds...
case MotionEvent.ACTION_UP:
// User raised his finger...
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
// Update the blending effect bitmap here and trigger a frame redraw,
// if you don't already have an animation thread to do it for you.
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
// You need to wait for this call back before attempting to draw
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
}
}
Then use it on the layout of your "drawing" activity like this:
<com.xxx.yyy.MainView
android:id="#+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
To draw to this surface you need the following code:
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (c != null)
c.drawBitmap(blendingImage, 0, 0, null); // Render blending effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
A fully functional example would be impractical to put in an answer so I recommend you to download the Lunar Lander sample game from Google for a full working example. Note however, that you won't need a game animation thread (although it won't hurt having one), like the one coded in the Lunar Lander sample, if all you need is the blending effect. The purpose of that thread is to create a game loop in which game frames are constantly generated to animate objects that may or may not depend on user input. In your case, all you need is to trigger a frame redraw after processing each touch event.
EDIT: The following code are fixes to get the code you've provided in the comments, working.
Here are the changes to MainActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// put pics from drawables to Bitmaps
Resources res = getResources();
BitmapDrawable bd1 = (BitmapDrawable) res.getDrawable(R.drawable.pic1);
// FIX: This block makes `operation` a mutable bitmap version of the loaded resource
// This is required because immutable bitmaps can't be changed
Bitmap tmp = bd1.getBitmap();
operation = Bitmap.createBitmap(tmp.getWidth(), tmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(operation);
Paint paint = new Paint();
c.drawBitmap(tmp, 0f, 0f, paint);
BitmapDrawable bd2 = (BitmapDrawable) res.getDrawable(R.drawable.pic2);
bmp = bd2.getBitmap();
myView = new MainView(this, operation, bmp);
FrameLayout preview = (FrameLayout) findViewById(R.id.preview);
preview.addView(myView);
}
...
Here are the changes to the MainView class:
public class MainView extends SurfaceView implements Callback {
private SurfaceHolder holder;
private Bitmap operation;
private Bitmap bmp2;
private boolean surfaceReady;
// took out AttributeSet attrs
public MainView(Context context, Bitmap operation, Bitmap bmp2) {
super(context);
this.operation = operation;
this.bmp2 = bmp2;
holder = getHolder(); // Fix: proper reference the instance variable
holder.addCallback(this); // Register this view as a surface change
// listener
setFocusable(true); // make sure we get key events
}
// Added so the blending operation is made in one place so it can be more easily upgraded
private void blend(int x, int y) {
if (x >= 0 && y >= 0 && x < bmp2.getWidth() && x < operation.getWidth() && y < bmp2.getHeight() && y < operation.getHeight())
operation.setPixel(x, y, bmp2.getPixel(x, y));
}
// Added so the drawing is now made in one place
private void drawOverlays() {
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized (holder) {
if (c != null)
c.drawBitmap(operation, 0, 0, null); // Render blending
// effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (!surfaceReady) // No attempt to blend or draw while surface isn't ready
return false;
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen. Falls through ACTION_MOVE once there is no break
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
blend((int) event.getX(), (int) event.getY());
}
// Update the blending effect bitmap here and trigger a frame
// redraw,
// if you don't already have an animation thread to do it for you.
drawOverlays();
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
surfaceReady = true;
drawOverlays();
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
surfaceReady = false;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
}
This code works for me. I just hope I didn't forget anything =:)
Let me know if you still have trouble, ok?
So the answer to this is that you're going to have to be a little clever, but it really shouldn't be so bad. Instead of posting all the code to do what you want to do, I'll give you a link here and an explanation.
So by managing the touch events of an application, you can figure out the average coordinates of a touch event. Using that information you can determine the center of all the pixels touched and continue to track that as a line is drawn with the finger. To track a straight line use the
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
clauses to determine the start and end of the line. If you want to track a line that is not straight and drawn, you're going to need a little more tracking using
case MotionEvent.ACTION_MOVE:
and that should get you fairly started. You may need a little bit of logic to deal with the fact that you will be drawing a very thin line and I suspect that's not quite what you're going for. Maybe it is though. Either way this should be a good place to get started.
EDIT
In regards to your first comment, here is a link you can use for an example. I have to make a small disclaimer though. To get all of the pieces to work together correctly, it may not seem that simple at first. I assure you that this is one of the simplest examples and breaks the tutorial into sections.
For what you would like to do, I think you'll want to pay particular attention to section 2 (no to be confused with step 2):
2. Facilitate Drawing
I suggest this because it shows different ways to use information form the TouchEvent. The things included in section 1 will explain a little bit about the environment to setup displaying a TouchEvent's captured data whereas section 3 is mostly about aesthetics. This may not directly answer your question, but I suspect it will get you where you need to be.
Happy coding! Leave a comment if you have any questions.
I have an onClickListener that triggers a network call so I would like to have some way to show the user that communications are in progress. The problem I am running into is that I can't seem to throw up a ProgressDialog or change the UI in any way for that matter before the call is made inside the onClick Listener. All of the code works just fine, but the UI changes don't come into effect until after all the code in onClickListener runs.
I was wondering if my problem is simply that an anonymous inner class like an onclicklistener can only update the UI at the end of its run? Or maybe my code is just bad.
Thanks in advance
Below is the code for the onclick Listener :
relayButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
cPanel.throwProgress(NCDTCPRelayActivity.this);
System.out.println(tvSocketConnection.getText().toString());
if (relayStatusArray[relayNumber] == 0)
{
if (cPanel.TurnOnRelay(relayNumber, 1) == false)
{
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
else {
if (cPanel.TurnOffRelay(relayNumber, 1) == false){
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
cPanel.hideProgress(NCDTCPRelayActivity.this);
}
});
Here is the code for the throwProgress and hideProgress respectively (these are in a subclass of the activity):
public boolean throwProgress(Context mContext) {
System.out.println("INSIDE THROWPROGRESS");
try {
tempDialog = ProgressDialog.show(mContext, "Connecting", "Connecting", true);
}
catch (RuntimeException e) {
return false;
}
return true;
}
public boolean hideProgress(Context mContext) {
System.out.println("OUTSIDE THROWPROGRESS");
tempDialog.hide();
return true;
}
**Edit
Here is the new code for the onClickListener that I put the runnable in:
public void onClick(View v) {
cPanel.throwProgress(NCDTCPRelayActivity.this);
System.out.println(tvSocketConnection.getText().toString());
handler.post(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
if (relayStatusArray[relayNumber] == 0)
{
if (cPanel.TurnOnRelay(relayNumber, 1) == false)
{
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
else {
if (cPanel.TurnOffRelay(relayNumber, 1) == false){
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
cPanel.hideProgress(NCDTCPRelayActivity.this);
relayStatusArray = cPanel.getBankStatus(1);
updateButtonText();
}
});
}
Changing UI from your click handler should work just fine. The problem is likely that you're doing some heavy work on the UI thread and it's blocking it so that the dialog is not really updated until after all of that work is done. Try moving all the heavy lifting into an AsyncTask (read this doc if you're unfamiliar with it) hiding the dialog when the task is complete and see if that fixes it.
All the UI updates are delayed on Android, as well as on pretty much every GUI platform out there. Changes to the looks of a view are never rendered right away; instead, the GUI subsystem marks the view as "needs redraw", and calls draw() some time later on the message loop.
If you want something to take place after the screen has been updated, use Handler.post(). The post()'ed code will execute some time on the message loop, typically later than the queued draw code.
Aside node: Windows GUI is a happy exception to that rule; you can draw on a Windows window outside WM_PAINT. But on Android you can't.