Reputation: 416
I have an app widget displaying timers. Timers can be started, stopped or resumed.
On Android 8 timers don't tick sometimes (50/50). Some users complained about the issue on Android 7 but I'm not absolutely sure if it's the same issue. Everything works well on Nexus 5 with Android 6 installed. If scroll down list view (until chronometer is invisible) and scroll up - timer starts ticking. If I put one more Chronometer above ListView and start - the chronometer is ticking well
ActivitiesRemoteViewsFactory
public RemoteViews getViewAt(int position) {
...
RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), getItemLayoutId());
if (timeLog.getState() == TimeLog.TimeLogState.RUNNING) {
remoteViews.setChronometer(R.id.widget_timer,
SystemClock.elapsedRealtime() - (duration * 1000 + System.currentTimeMillis() - timeLog.getStartDate().getTime()), null, true);
} else {
long timerValue = SystemClock.elapsedRealtime() - duration * 1000;
remoteViews.setChronometer(R.id.widget_timer, timerValue, null, false);
}
return remoteViews;
Update is sent from AppWidgetProvider's onReceive method
public void onReceive(Context context, Intent intent) {
AppWidgetManager widgetManager = AppWidgetManager.getInstance(ctx);
...
widgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_activities_list);
}
TargetSDK is 25
UPDATE The issue is also reproduced in main app. Something is wrong with listView as it works well in RecyclerView. Added simple example reproducing the issue at https://github.com/zaplitny/WidgetIssue
Upvotes: 14
Views: 1410
Reputation: 554
Add this in you application manifest :
<!-- Widget Receiver -->
<receiver android:name=".widgets.WidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider" />
</receiver>
Now when you want to update widget from application call this:
try {
Intent intent = new Intent(ctxt, WidgetProvider.class);
intent.setAction("android.appwidget.action.APPWIDGET_UPDATE");
int ids[] = AppWidgetManager.getInstance(ctxt).getAppWidgetIds(new ComponentName(ctxt, WidgetProvider.class));
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,ids);
ctxt.sendBroadcast(intent);
} catch (Exception e) {
e.printStackTrace();
}
I have used this to update my widget. Thank you.
Upvotes: 0
Reputation: 62831
The Chronometer
widget was updated between API 25 and API 26 to prevent non-visible timers from updating. (That is my best guess from looking at the underlying code.) Specifically, the updateRunning()
method was changed to take into account whether the widget is shown or not. From Chronometer.java
:
For API 25: boolean running = mVisible && mStarted;
For API 26: boolean running = mVisible && mStarted && isShown();
That isShown()
is the source of your problem and it stops your Chronometers
from being reused. For API 26+, reused Chronometers
do not get updated, it seems, when they are in a ListView
.
There are several ways to take care of this issue. The first is to not reuse Chronometers
. In getView()
of your adapters, you can use the following code:
if (view == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
view = LayoutInflater.from(getContext()).inflate(R.layout.widget_timer, parent, false);
}
With this code, you will always create new Chronometers
and never reuse them.
An alternative to this is to call chronometer.start()
after the widget is laid out and isShown()
will be true. Add the following code to getView()
:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
chronometer.start();
} else {
view.post(new Runnable() {
@Override
public void run() {
chronometer.start();
}
});
}
Either way works and there are other ways.
Upvotes: 10