Widget with ListView shows 'Loading' for each item found
Asked Answered
R

1

13

I'm trying to build a widget for my Android app and I'm running into the problem that while the ListView does find data (through the debugger I can see the getViewAt method in my RemoteViewFactory implementation is called and returning a proper RemoteViews), each entry found is simply shown in my widget as "Loading..."

Android emulator screenshot showing widget with four entries, all four showing 'loading'

For the record: there are indeed 4 entries found in my database and the getViewAt method is called four times, each time returning a proper list item.

I've based my code off the Android documentation, having used the StackView sample as technical reference.

In trying to fix this problem I've consulted stackoverflow and found some past questions. The most common suggestions for fixes are:

  • Ensure the getViewTypeCount method returns a value other than 0 (usually 1, as most widgets will only use one view type);
  • Ensure only supported views are used;
  • A few more vague suggestions as some arbitrary view properties should or shouldn't be used.

I've tried all of the suggestions above and am using only supported views yet still my items aren't displayed. Does someone here have an idea?

A list of relevant code I've written:

Entries added to my AndroidManifest.xml file

    <service
        android:name=".widget.DfdWidgetService"
        android:permission="android.permission.BIND_REMOTEVIEWS" />

    <receiver android:name=".widget.DfdWidget">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>

        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/dfd_widget_info" />
    </receiver>

Widget layout xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80DDE8ED"
android:padding="@dimen/widget_margin">

<ListView
    android:id="@+id/widgetRemindersListView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:listitem="@layout/dfd_widget_list_item">

</ListView>

<TextView
    android:id="@+id/widgetEmptyViewText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="You have no reminders for today, nice!"
    android:textAlignment="center"
    android:textSize="20sp" />

</RelativeLayout>

Widget list item xml

(As a test I did try removing everything except the TextViews to see if Android didn't find the ImageViews offending. It didn't matter.)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widgetListItem"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
    android:id="@+id/widgetCompletedCheckBox"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_alignParentStart="true"
    android:layout_marginStart="10dp"
    android:layout_marginTop="15dp"
    android:layout_marginEnd="10dp"
    android:layout_marginBottom="10dp"
    app:srcCompat="@android:drawable/checkbox_off_background" />

<TextView
    android:id="@+id/widgetReminderName"
    android:layout_width="match_parent"
    android:layout_height="30dp"
    android:layout_toEndOf="@id/widgetCompletedCheckBox"
    android:text="Reminder name"
    android:textColor="#000000"
    android:textSize="24sp" />

<TextView
    android:id="@+id/widgetReminderDate"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/widgetReminderName"
    android:layout_toEndOf="@id/widgetCompletedCheckBox"
    android:text="Ma 1 jan"
    android:textColor="#2196F3" />

<ImageView
    android:id="@+id/widgetReminderRepeating"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_below="@id/widgetReminderName"
    android:layout_toEndOf="@id/widgetReminderDate"
    android:background="#00FFFFFF"
    app:srcCompat="@android:drawable/ic_menu_revert"
    tools:visibility="invisible" />

<ImageView
    android:id="@+id/widgetIsImportant"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_alignParentEnd="true"
    app:srcCompat="@android:drawable/btn_star_big_off" />
</RelativeLayout>

Widget Provider class

public class DfdWidget extends AppWidgetProvider {

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
        Intent intent = new Intent(context, DfdWidgetService.class);

        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.dfd_widget);
        remoteViews.setRemoteAdapter(R.id.widgetRemindersListView, intent);
        remoteViews.setEmptyView(R.id.widgetRemindersListView, R.id.widgetEmptyViewText);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
    }

    @Override
    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
        super.onRestored(context, oldWidgetIds, newWidgetIds);
    }
}

Widget Service Class

public class DfdWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new DfdRemoteViewsFactory(getApplicationContext());
    }

    class DfdRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
        private final Context context;
        private ReminderRepository reminderRepository;
        private List<Reminder> reminders;

        public DfdRemoteViewsFactory(Context context) {
            this.context = context;
            this.reminderRepository = ReminderRepository.getReminderRepository(context);
        }

        @Override
        public void onCreate() {
            reminders = reminderRepository.getAllRemindersForTodayList();
        }

        @Override
        public void onDataSetChanged() {
            reminders = reminderRepository.getAllRemindersForTodayList();
        }

        @Override
        public void onDestroy() {
            reminders.clear();
        }

        @Override
        public int getCount() {
            return reminders.size();
        }

        @Override
        public RemoteViews getViewAt(int position) {
            Reminder reminder = reminders.get(position);
            RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.id.widgetListItem);

            remoteView.setTextViewText(R.id.widgetReminderName, reminder.getName());
            // Removed code that sets the other fields as I tried it with and without and it didn't matter. So removed for brevity

            return remoteView;
        }

        @Override
        public RemoteViews getLoadingView() {
            return null;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public long getItemId(int position) {
            return reminders.get(position).getId();
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }
    }
}
Realistic answered 3/5, 2020 at 17:55 Comment(3)
RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.id.widgetListItem); – In getViewAt(), that should be an R.layout, not an R.id. Probably just a typo, assuming you named the layout the same thing. Also, not relevant to the current issue, but anything that uses the app namespace prefix in your layouts isn't going to work in RemoteViews; e.g., app:srcCompat. Just FYI.Holozoic
Actually, you hit the nail right on the head with this comment! Both you and CommonsWare were really quick in finding this!Realistic
@Realistic i want to know how to send data Widget Provider class to Widget Service Class? where is function that set values on reminderRepository?Dyche
S
6
RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.id.widgetListItem)

The RemoteViews constructor that you are trying to use takes a layout resource ID as the second parameter. You are passing in a widget ID.

So, replace R.id.widgetListItem with R.layout.whateverYourLayoutNameIsForTheListItems, where you replace whateverYourLayoutNameIsForTheListItems with whatever your layout name is for the list items.

Shadbush answered 3/5, 2020 at 18:3 Comment(2)
I don't know how you and Mike M found it so quickly, but cheers!Realistic
@Lynxian: The fact that the proper count was showing up indicated, as you pointed out, that your RemoteViewsService was getting called. A quick examination of the row layout XML suggested that the "Loading..." text was not from your code, which implies that the widget host was having difficulty using your rows. That limited the problem to be where you set up the RemoteViews for the rows, and that was only a few lines of code.Shadbush

© 2022 - 2024 — McMap. All rights reserved.