I’m trying to make an app widget that changes the TextView value frequently — a stopwatch widget. To do this I’m using a runnable that I start in a handler to update the values, then set the textview to those values. For some reason, the textview does not update though. Everything works properly, the textview just won’t update.
Here is the code for the Handler and Runnable:
RemoteViews remoteViews;
long sStart, elapsedTime;
private final int REFRESH_RATE = 100;
private Handler sHandler = new Handler();
private Runnable startStopwatch = new Runnable() {
@Override
public void run() {
final long start = sStart;
elapsedTime = System.currentTimeMillis() - start;
updateStopwatch(elapsedTime);
sHandler.postDelayed(this, REFRESH_RATE);
}
};
private void updateStopwatch(long time) {
int seconds = (int) time / 1000;
int minutes = seconds / 60;
int hours = minutes / 60;
long millis = time % 1000;
seconds = seconds % 60;
remoteViews.setTextViewText(R.id.tvStopwatchWidget, String.format("%d : %02d : %02d", hours, minutes, seconds));
remoteViews.setTextViewText(R.id.tvStopwatchMillisWidget, String.format(". %03d", millis));
}
I set everything up in the onUpdate method, it all works properly. The onReceive method, where I should update the textview on a button click, is this:
@Override
public void onReceive(Context context, Intent intent) {
remoteViews = new RemoteViews(context.getPackageName(), R.layout.stopwatch_widget_layout);
if (intent.getAction().equals(ACTION_STOPWATCH_WIDGET_START)) {
if (sStart == 0L) {
sStart = System.currentTimeMillis();
} else {
sStart = System.currentTimeMillis() - elapsedTime;
}
sHandler.removeCallbacks(startStopwatch);
sHandler.postDelayed(startStopwatch, REFRESH_RATE);
}
ComponentName widget = new ComponentName(context, StopwatchWidgetProvider.class);
AppWidgetManager.getInstance(context).updateAppWidget(widget, remoteViews);
super.onReceive(context, intent);
}
This is the same code I use to update it for the actual app, which works, only this is modified for the app widget. I know that the time is properly updating — if I display it in the log it updates the time correctly. I also know that the textview will properly change, if I change it directly in the onReceive method. Everything seems to be working, but for some reason it won’t update the times when it tries to in the Runnable, so I’m really not sure how to fix this.
I’ve also tried it using a service, but that didn’t work any better. The code for it is:
public class SWStartService extends Service {
RemoteViews remoteViews;
long sStart = 0;
long elapsedTime;
private final int REFRESH_RATE = 100;
private Handler sHandler = new Handler();
private Runnable startStopwatch = new Runnable() {
@Override
public void run() {
final long start = sStart;
elapsedTime = System.currentTimeMillis() - start;
updateStopwatch(elapsedTime);
sHandler.postDelayed(this, REFRESH_RATE);
}
};
String sMain, sMillis;
private void updateStopwatch(long time) {
int seconds = (int) time / 1000;
int minutes = seconds / 60;
int hours = minutes / 60;
long millis = time % 1000;
seconds = seconds % 60;
remoteViews.setTextViewText(R.id.tvStopwatchWidget, String.format("%d : %02d : %02d", hours, minutes, seconds));
remoteViews.setTextViewText(R.id.tvStopwatchMillisWidget, String.format(". %03d", millis));
Intent i1 = new Intent(getApplicationContext(), SWStartService.class);
sMain = String.format("%d : %02d : %02d", hours, minutes, seconds);
sMillis = String.format(". %03d", millis);
// Log.d("TEST TIME", String.format("%d : %02d : %02d . %03d", hours, minutes, seconds, millis));
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
remoteViews = new RemoteViews(getApplicationContext().getPackageName(), R.layout.stopwatch_widget_layout);
if (sStart == 0L) {
sStart = System.currentTimeMillis();
} else {
sStart = System.currentTimeMillis() - elapsedTime;
}
sHandler.removeCallbacks(startStopwatch);
sHandler.postDelayed(startStopwatch, 100);
intent.putExtra("sMainWidget", sMain);
intent.putExtra("sMillisWidget", sMillis);
sendBroadcast(intent);
return START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
That service is called in the onReceive method like this:
Intent intent1 = new Intent(context, SWStartService.class);
context.startService(intent1);
I’ve tried a few other techniques to start the service, I still can’t get the TextView to update though.
I know it’s pretty sloppy, I tried several different techniques to get it working, but none of it worked and I didn’t clean up the old techniques. It didn’t work with changing the remoteViews directly, or with changing them in the appwidget class by getting the extras from the intent. Everything just returns blank/null — the intent extras, or the remoteViews, in or out of the service for some reason. And I realize this might not be very good for the battery or very efficient, but it’s bothering me that I can’t even update the TextView at all.
Any suggestions?
Edit: Someone might want the solution, so here:
//Global Variables
ComponentName widget;
AppWidgetManager awManager;
//Set them up in onReceive method
remoteViews = new RemoteViews(context.getPackageName(), R.layout.stopwatch_widget_layout);
widget = new ComponentName(context, StopwatchWidgetProvider.class);
awManager = AppWidgetManager.getInstance(context);
if (intent.getAction().equals(ACTION_STOPWATCH_WIDGET_START)) {
//showStopButton();
if (sStart == 0) {
sStart = System.currentTimeMillis();
} else {
sStart = System.currentTimeMillis() - elapsedTime;
}
sHandler.removeCallbacks(startStopwatch);
sHandler.postDelayed(startStopwatch, REFRESH_RATE);
}
awManager.updateAppWidget(widget, remoteViews);
super.onReceive(context, intent);
//update appwidget in runnable
final long start = sStart;
elapsedTime = System.currentTimeMillis() - start;
updateStopwatch(elapsedTime);
sHandler.postDelayed(this, REFRESH_RATE);
awManager.updateAppWidget(widget, remoteViews);
You only call
AppWidgetManager.updateAppWidget(widget, remoteViews)once. You need to call it every time you update the widget.