定制的 ListView 在立即填充数据。

huangapple go评论81阅读模式
英文:

Android: custom ListView doesn't get populated immediately

问题

我想创建一个自定义的ListView,并使用自定义的ArrayAdapter来填充数据。代码能够正常工作,但是ListView不会立即显示数据。为了让数据显示出来,我需要点击同一页上的任意EditText,关闭键盘,然后神奇地ListView就会显示数据。
我不知道这是否会产生影响,但是适配器是在一个片段(Fragment)中使用的,在片段的OnCreate方法中从Firebase检索数据。
以下是我的代码,我已经删除了与此主题无关的部分,并简化了数组。

这是数组中的元素类:

public class ListItem {

    private String Description;
    private String Price;

    public ListItem (String Description, String Price) {
        this.Description = Description;
        this.Price = Price;
    }

    public String getDescription() { return Description; }

    public String getPrice() {
        return Price;
    }

    public void setDescription(String description) {
        Description = description;
    }

    public void setPrice(String price) {
        Price = price;
    }

}

适配器如下:

public class CustomListAdapter extends ArrayAdapter<ListItem> {

    private Context mContext;
    private Integer mResource;

    private static class ViewHolder {
        TextView txt_description_item;
        TextView txt_price_item;
    }

    public CustomListAdapter(@NonNull Context context, int resource, @NonNull ArrayList<ListItem> objects) {
        super(context, resource, objects);
        mContext = context;
        mResource = resource;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        
        String Description = getItem(position).getDescription();
        String Price = getItem(position).getPrice();

        LayoutInflater inflater = LayoutInflater.from(mContext);
        convertView = inflater.inflate(mResource, parent, false);

        ViewHolder viewHolder = new ViewHolder();

        viewHolder.txt_description_item = convertView.findViewById(R.id.txt_description_item);
        viewHolder.txt_price_item = convertView.findViewById(R.id.txt_price_item);

        viewHolder.txt_description_item.setText(Description);
        viewHolder.txt_price_item.setText(Price);

        return convertView;
    }

}

最后,这是片段(Fragment)的代码:

public class MyFragment extends Fragment {

    private ListView view_list_items;
    private FirebaseDatabase database;
    private DatabaseReference refDatabase;
    private static final String USER = "user";

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        final View v = inflater.inflate(R.layout.fragment_myprofile, container, false);
        view_list_items = v.findViewById(R.id.list_items);

        database = FirebaseDatabase.getInstance();
        refDatabase = database.getReference(USER);

        refDatabase.child("items").addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                
                final ArrayList<ListItem> arr_items = new ArrayList<>();

                for (DataSnapshot ds : dataSnapshot.child("itemlist1").getChildren()) {
                    final String description = ds.getKey();
                    final String price = ds.getValue(String.class);

                    ListItem listItem = new ListItem(description,price);
                    arr_items.add(listItem);
                }

                CustomListAdapter adapter = new CustomListAdapter(getContext(),R.layout.layout_myItemList, arr_items);               
                view_list_items.setAdapter(adapter);
            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });

        return v;
    }
}

编辑
我通过在for循环内设置适配器来解决了这个问题。也许这不是最优雅的解决方案,但它可以正常工作。

for (DataSnapshot ds : dataSnapshot.child("itemlist1").getChildren()) {
    final String description = ds.getKey();
    final String price = ds.getValue(String.class);

    ListItem listItem = new ListItem(description, price);
    arr_items.add(listItem);

    CustomListAdapter adapter = new CustomListAdapter(getContext(), R.layout.layout_myItemList, arr_items);               
    view_list_items.setAdapter(adapter);
}
英文:

I would like to create a custom ListView populated by a custom ArrayAdapter. The code works fine, but the ListView is not showing data immediately. In order to have the data shown I need to click on a random EditText of the same page, close the Keyboard and magically the ListView shows data.
I don't know if it this can have an impact but the adapter is used in a fragment,which retrieve the data from Firebase during the OnCreate method.
Below my code, I removed everything not necessary for this topic and simplified the array.

This is my Class of elements inside the array:

public class ListItem {
private String Description;
private String Price;
public ListItem (String Description, String Price) {
this.Description = Description;
this.Price = Price;
}
public String getDescription() { return Description; }
public String getPrice() {
return Price;
}
public void setDescription(String description) {
Description = description;
}
public void setPrice(String price) {
Price = price;
}
}

The adapter is the following:

public class CustomListAdapter extends ArrayAdapter&lt;ListItem&gt; {
private Context mContext;
private Integer mResource;
private static class ViewHolder {
TextView txt_description_item;
TextView txt_price_item;
}
public CustomListAdapter(@NonNull Context context, int resource, @NonNull ArrayList&lt;ListItem&gt; objects) {
super(context, resource, objects);
mContext = context;
mResource = resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
String Description = getItem(position).getDescription();
String Price = getItem(position).getPrice();
LayoutInflater inflater = LayoutInflater.from(mContext);
convertView = inflater.inflate(mResource, parent, false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.txt_description_item = convertView.findViewById(R.id.txt_description_item);
viewHolder.txt_price_item = convertView.findViewById(R.id.txt_price_item);
viewHolder.txt_description_item.setText(Description);
viewHolder.txt_price_item.setText(Price);
return convertView;
}
}

Finally this is the Fragment code:

public class MyFragment extends Fragment {
private ListView view_list_items;
private FirebaseDatabase database;
private DatabaseReference refDatabase;
private static final String USER = &quot;user&quot;;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.fragment_myprofile, container, false);
view_list_items = v.findViewById(R.id.list_items);
database = FirebaseDatabase.getInstance();
refDatabase = database.getReference(USER);
refDatabase.child(&quot;items&quot;).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
final ArrayList&lt;ListItem&gt; arr_items = new ArrayList&lt;&gt;();
for (DataSnapshot ds : dataSnapshot.child(&quot;itemlist1&quot;).getChildren()) {
final String description = ds.getKey();
final String price = ds.getValue(String.class);
ListItem listItem = new ListItem(description,price);
arr_items.add(listItem);
}
CustomListAdapter adapter = new CustomListAdapter(getContext(),R.layout.layout_myItemList, arr_items);               
view_list_items.setAdapter(adapter);
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
}

EDIT:
I solved the issue by setting the adapter inside the 'for' cycle. Maybe it's not the most elegant solution but it works fine

for (DataSnapshot ds : dataSnapshot.child(&quot;itemlist1&quot;).getChildren()) {
final String description = ds.getKey();
final String price = ds.getValue(String.class);
ListItem listItem = new ListItem(description,price);
arr_items.add(listItem);
CustomListAdapter adapter = new 
CustomListAdapter(getContext(),R.layout.layout_myItemList, arr_items);               
view_list_items.setAdapter(adapter);
}

答案1

得分: 1

Firebase数据库是异步的,即编译器不会等待onDataChange()onCancelled()方法内的代码返回值。除了这两个方法内部的值,它会立即返回所有其他的值,而对于这两个方法,它会在值从数据库中可用时返回该值。以下代码可能会帮助您更好地理解:

refDatabase.child("items").addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

        System.out.println("Inside onDataChange method");
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {

    }
});
System.out.println("Outside onDataChange method");

输出将类似于:

Outside onDataChange method
Inside onDataChange method

请注意,首先会显示Outside onDataChange method。这就是为什么您的ListView不会立即显示数据的原因。为了让用户保持参与,您可以在视图中添加一个旋转器,该旋转器将在从数据库获取数据之前可见。要实现此目的,尝试以下代码:

database = FirebaseDatabase.getInstance();
refDatabase = database.getReference(USER);
spinner.setVisibility(View.VISIBLE);

refDatabase.child("items").addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

        spinner.setVisibility(View.INVISIBLE);
        // 在这里添加您的代码
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        // 在这里添加错误处理代码
    }
});
英文:

The firebase database is asynchronous i.e, the compiler doesn't wait for the code inside onDataChange() or onCancelled() to return a value. It would return all the values apart from those inside those 2 methods immediately, while for those 2 methods it would return the value when the value is available from the database. Below code might help you in better understanding

 refDatabase.child(&quot;items&quot;).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
System.out.println(&quot;Inside onDataChange method&quot;);
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
System.out.println (&quot;Outside onDataChange method&quot;);

The output will be like:

Outside onDataChange method
Inside onDataChange method

Notice that comes Outside onDataChange method first. This is the reason why your listView doesn't show data immediately.
What you can do to keep the user engaged is, add a spinner in your view which will be visible till the data is fetched from the database. To do this, try this code.

        database = FirebaseDatabase.getInstance();
refDatabase = database.getReference(USER);
spinner.setVisibility(View.VISIBLE);
refDatabase.child(&quot;items&quot;).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
spinner.setVisibility(View.INVISIBLE);
//Your code goes here
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
//Error code goes here
}
});

huangapple
  • 本文由 发表于 2020年4月5日 19:11:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/61041638.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定