I am trying to do a simple widget for my app, which displays data from an API on the widget using ListView, but the list is empty even tho Logs says that the data was downloaded successfully. Blank listView is displayed with no data in it. No errors are shown. What's the cause of it?
Resources I used when tried to solve this problem:
official documentation - https://developer.android.com/guide/topics/appwidgets#collections
similar question - How to use listview in a widget?
my AppWidgetProvider
public class Widget extends AppWidgetProvider {
RemoteViews views;
void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
CharSequence widgetText = context.getString(R.string.appwidget_text);
// Construct the RemoteViews object
views = new RemoteViews(context.getPackageName(), R.layout.my_widget);
views.setTextViewText(R.id.appwidget_text, widgetText);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@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) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget);
Intent intent = new Intent(context, WidgetService.class);
remoteViews.setRemoteAdapter(R.id.appwidget_list_view, intent);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
}
WidgetFactory and WidgetService
public class WidgetFactory implements RemoteViewsService.RemoteViewsFactory{
final Context context;
List<SimpleCoin> list;
List<Coin> coinList;
public WidgetFactory(Context context, Intent intent) {
this.context = context;
}
@Override
public void onCreate() {
//get data from database
CoinDatabase coinDatabase = CoinDatabase.buildDatabase(context);
list = coinDatabase.coinDao().getAllSimpleCoinsNonLive();
coinList = new ArrayList<>();
}
@Override
public void onDataSetChanged() {
//based on data from database download content (JSON)
if(list != null) {
Log.d("Widget", "Coin size -> " + list.size());
StringBuilder id = new StringBuilder("id=" + list.get(0).getId());
for (int j = 0; j < list.size(); j++) {
id.append(",");
id.append(list.get(j).getId());
}
String finalUrl = URL + id.toString() + API_KEY;
Observable.fromCallable(() -> {
HttpURLConnection connection = null;
BufferedReader reader = null;
java.net.URL url1 = new URL(finalUrl);
connection = (HttpURLConnection) url1.openConnection();
connection.connect();
InputStream stream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuilder buffer = new StringBuilder();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line).append("\n");
}
connection.disconnect();
reader.close();
return buffer.toString();
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull String s) {
if (s != null) {
try {
//Create new coin and update list of coins
JSONObject object = new JSONObject(s);
for (int i = 0; i < list.size(); i++) {
JSONObject jsonObject = object.getJSONObject("data").getJSONObject(String.valueOf(list.get(i).getId()));
Log.d("Widget", "Coin -> " + jsonObject.getString("name") + jsonObject.getJSONObject("quote").getJSONObject("USD").getDouble("price") + jsonObject.getJSONObject("quote").getJSONObject("USD").getDouble("percent_change_7d"));
Coin coin = new Coin(jsonObject.getString("name"), jsonObject.getJSONObject("quote").getJSONObject("USD").getDouble("price"), jsonObject.getJSONObject("quote").getJSONObject("USD").getDouble("percent_change_7d"), false);
coinList.add(coin);
getViewAt(i);
}
} catch (JSONException e) {
Log.d("Widget", "Coin -> " + e.toString());
e.printStackTrace();
}
}
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
}
}
@Override
public void onDestroy() {}
@Override
public int getCount() {
return list.size();
}
@Override
public RemoteViews getViewAt(int i) {
//Set ListData based on coinList
Log.d("Widget", "Coin size getViewAt -> " + list.size() + "item num ->" + i);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget_list_item);
if(coinList != null)
if(coinList.size() > i) {
Log.d("Widget", "Coin position added" + coinList.get(i).getName());
remoteViews.setTextViewText(R.id.widgetItemTaskNameLabel, coinList.get(i).getName());
}
return remoteViews;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public boolean hasStableIds() {
return true;
}
}
public class WidgetService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new WidgetFactory(this.getApplicationContext(), intent);
}
}
Here are my logs:
D/Widget: Coin size getViewAt -> 1 item num ->0
D/Widget: Coin size getViewAt -> 1 item num ->0
D/Widget: Coin -> testCoin0.00.0
D/Widget: Coin size getViewAt -> 1 item num ->0
D/Widget: Coin position added testCoin
my_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/colorPrimary"
android:padding="@dimen/widget_margin">
<TextView
android:id="@+id/appwidget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_margin="8dp"
android:background="@color/colorPrimary"
android:contentDescription="@string/appwidget_text"
android:text="@string/appwidget_text"
android:textColor="#ffffff"
android:textSize="24sp"
android:textStyle="bold|italic" />
<ListView
android:id="@+id/appwidget_list_view"
android:layout_width="match_parent"
android:layout_margin="8dp"
tools:listitem="@layout/my_widget_list_item"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:layout_below="@id/appwidget_text">
</ListView>
</RelativeLayout>
my_widget_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:paddingLeft="@dimen/widget_listview_padding_x"
android:paddingRight="@dimen/widget_listview_padding_x"
android:paddingStart="@dimen/widget_listview_padding_x"
android:paddingEnd="@dimen/widget_listview_padding_x"
android:minHeight="@dimen/widget_listview_item_height"
android:weightSum="2"
android:layout_height="wrap_content">
<TextView
android:id="@+id/widgetItemTaskNameLabel"
android:layout_width="0dp"
android:gravity="start"
android:layout_weight="1"
android:textColor="@color/colorPrimary"
android:layout_gravity="center_vertical"
android:layout_height="wrap_content" />
</LinearLayout>
I found my mistake, turns out my API call shouldn't be asynchronous because as the documentation says:
public void onDataSetChanged() -> You can do heaving lifting in here, synchronously. For example, if you need to process an image, fetch something from the network, etc., it is ok to do it here, synchronously. The widget will remain in its current state while work is being done here, so you don't need to worry about locking up the widget.
So getting my data from Asynchronous call was the reason why data wasn't displayed