Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chronometer doesn't update in app widget ListView

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

like image 468
zaplitny Avatar asked Feb 28 '18 09:02

zaplitny


1 Answers

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.

like image 168
Cheticamp Avatar answered Oct 21 '22 23:10

Cheticamp