Search code examples
androidlistviewandroid-fragmentscustom-adapternotifydatasetchanged

ListView and custom Adapter using Firebase


In my application I have a section (fragment) where I want to show the plants and their quantities (stored on Firebase) through a ListView. By using a floating action button I allow to update the quantity related to a plant if it already is in the db, otherwise to add it.

Here my first two problems:

#1 This fragment is shown in the main activity, so at the startup I would like that the data retrieved from the database are shown immediately, but this doesn't happen, they're shown only after adding/updating a plant.

#2 Here is my code:

public class MyVegGardenFragment extends Fragment {

    private View v;
    private ListView listView;
    private DatabaseReference database;
    private FirebaseAuth auth;
    private FirebaseUser us;
    private Map<String, Integer> myVegGarden;
    private ArrayList<String> plantsList = new ArrayList<>();
    private ArrayList<Integer> quantityList = new ArrayList<>();
    private MyVegGardenAdapter adapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {


        myVegGarden = new HashMap<>();

        database = FirebaseDatabase.getInstance().getReference();
        auth = FirebaseAuth.getInstance();
        us = auth.getCurrentUser();

        database.addValueEventListener(new ValueEventListener() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {

                User u = dataSnapshot.child("users").child(us.getUid()).getValue(User.class);
                if (u != null) {
                    myVegGarden = u.getMyVegGarden();
                    if (myVegGarden != null) {
                        for(Map.Entry<String, Integer> entry : myVegGarden.entrySet()) {
                            plantsList.add(entry.getKey());
                            quantityList.add(entry.getValue());
                        }
                    }
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
            }
        });


        v = inflater.inflate(R.layout.myveggarden_fragment_layout, container, false);
        listView = (ListView) v.findViewById(R.id.myVegGarden_list_view);
        adapter = new MyVegGardenAdapter(getActivity(), plantsList, quantityList);
        listView.setAdapter(adapter);

        FloatingActionButton fab = (FloatingActionButton) v.findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder;
                // [...] leaving code for AlertDialog working correctly
                builder.setPositiveButton("PLANT", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                        final String plant = options[plant_picker.getValue()];
                        final int quantity = quantity_picker.getValue();


                        if(!adapter.getPlantsList().contains(plant)){
                            adapter.add(plant, quantity);
                        }
                        else {
                            adapter.updateIfPresent(plant, quantity);
                        }

                        //update db value
                        database.addValueEventListener(new ValueEventListener() {
                            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
                            @Override
                            public void onDataChange(DataSnapshot dataSnapshot) {

                                User u = dataSnapshot.child("users").child(us.getUid()).getValue(User.class);
                                if (u != null) {
                                    myVegGarden = u.getMyVegGarden();
                                    if (myVegGarden != null) {
                                        myVegGarden.put(plant, quantity);
                                    }
                                    else {
                                        myVegGarden = new HashMap<>();
                                        myVegGarden.put(plant, quantity);
                                        }
                                    database.child("users").child(us.getUid()).child("myVegGarden").setValue(myVegGarden);
                                    }
                            }

                            @Override
                            public void onCancelled(DatabaseError databaseError) {
                            }
                        });
                       dialog.dismiss();
                       }
                });

                builder.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    } });

                AlertDialog dialog = builder.create();
                dialog.show();
            }
        });

        return v;
    }

}
public class MyVegGardenAdapter extends BaseAdapter {

    private Context context;
    private LayoutInflater inflater;
    private ArrayList<String> dataSource;
    private ArrayList<Integer> quantity;

    public MyVegGardenAdapter(Context context, ArrayList<String> items, ArrayList<Integer> quantity) {
        this.context = context;
        dataSource = items;
        this.quantity = quantity;
        //line 31
        inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    }

    public void add(String plant, Integer qty) {
        dataSource.add(plant);
        quantity.add(qty);
        notifyDataSetChanged();
    }

    public void updateIfPresent(String plant, Integer qty) {
        quantity.set(dataSource.indexOf(plant), qty);
        notifyDataSetChanged();
    }

    public List<String> getPlantsList() { return dataSource; }

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

    @Override
    public Object getItem(int position) {
        return dataSource.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    public Object getQuantity(int position) {
        return quantity.get(position);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Get view for row item
        View rowView = inflater.inflate(R.layout.listview_row, parent, false);

        // Get title element
        TextView firstLine = (TextView) rowView.findViewById(R.id.firstLine);
        // Get subtitle element
        TextView secondLine = (TextView) rowView.findViewById(R.id.secondLine);
        // Get icon element
        ImageView icon = (ImageView) rowView.findViewById(R.id.list_icon);

        String plant = (String) getItem(position);
        Integer quantity = (Integer) getQuantity(position);

        firstLine.setText(plant);
        secondLine.setText(Html.fromHtml("<b>Quantity: </b>" + quantity));

        switch(plant) {
            case "Bean":
                icon.setImageResource(R.drawable.ic_bean_48dp); break;
            case "Cabbage":
                icon.setImageResource(R.drawable.ic_cabbage_48dp); break;
            case "Carrot":
                icon.setImageResource(R.drawable.ic_carrot_48dp); break;
            case "Cucumber":
                icon.setImageResource(R.drawable.ic_cucumber_48dp); break;
            case "Eggplant":
                icon.setImageResource(R.drawable.ic_eggplant_48dp); break;
            case "Lettuce":
                icon.setImageResource(R.drawable.ic_lettuce_48dp); break;
            case "Pea":
                icon.setImageResource(R.drawable.ic_pea_48dp); break;
            case "Pepper":
                icon.setImageResource(R.drawable.ic_pepper_48dp); break;
            case "Radish":
                icon.setImageResource(R.drawable.ic_radish_48dp); break;
            case "Tomato":
                icon.setImageResource(R.drawable.ic_tomato_48dp); break;
        }
        return rowView;
    }
}

Well... the database is always correctly updated, but: sometimes plantsList and quantityLists insert replicas, so plants appear twice or thrice and I don't want it.

EDIT: I succeeded in solving these two problems. Now there's just the following problem. Quite often this error occurs:

10-19 19:28:52.460 19148-19148/.veggarden E/AndroidRuntime: FATAL EXCEPTION: main Process: .veggarden, PID: 19148 java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference at .veggarden.MyVegGardenAdapter.(MyVegGardenAdapter.java:31) at .veggarden.MyVegGardenFragment$1.onDataChange(MyVegGardenFragment.java:85) at com.google.android.gms.internal.zzbmz.zza(Unknown Source) at com.google.android.gms.internal.zzbnz.zzYj(Unknown Source) at com.google.android.gms.internal.zzboc$1.run(Unknown Source) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Until now this error occurs:

  1. the first time I try to add a plant when I deleted my plant from db (context is not null, but after adding a plant it is)
  2. when I go to another fragment through a bottom toolbar and then I come back to this fragment to add a plant
  3. when adding many plants (rarely and not so sure)

Here my up-to-date code:

public class MyVegGardenFragment extends Fragment {

    private View v;
    private ListView listView;
    private DatabaseReference database;
    private FirebaseAuth auth;
    private FirebaseUser us;
    private Map<String, Integer> myVegGarden;
    private ArrayList<String> plantsList = new ArrayList<>();
    private ArrayList<Integer> quantityList = new ArrayList<>();
    private MyVegGardenAdapter adapter;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {


        myVegGarden = new HashMap<>();

        database = FirebaseDatabase.getInstance().getReference();
        auth = FirebaseAuth.getInstance();
        us = auth.getCurrentUser();

        v = inflater.inflate(R.layout.myveggarden_fragment_layout, container, false);
        listView = (ListView) v.findViewById(R.id.myVegGarden_list_view);

        database.addValueEventListener(new ValueEventListener() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {

                plantsList = new ArrayList<String>();
                quantityList = new ArrayList<Integer>();

                User u = dataSnapshot.child("users").child(us.getUid()).getValue(User.class);
                if (u != null) {
                    myVegGarden = u.getMyVegGarden();
                    if (myVegGarden != null) {
                        for(Map.Entry<String, Integer> entry : myVegGarden.entrySet()) {
                            plantsList.add(entry.getKey());
                            quantityList.add(entry.getValue());
                        }
                    }
                    **line 85**
                    adapter = new MyVegGardenAdapter(getActivity(), plantsList, quantityList); 
                    listView.setAdapter(adapter);
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
            }
        });


        FloatingActionButton fab = (FloatingActionButton) v.findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder;
                // [...] leaving code for AlertDialog working correctly
                builder.setPositiveButton("PLANT", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        final String plant = options[plant_picker.getValue()];
                        final int quantity = quantity_picker.getValue();

                        if(!adapter.getPlantsList().contains(plant)){
                            adapter.add(plant, quantity);
                        }
                        else {
                            adapter.updateIfPresent(plant, quantity);
                        }

                        if (myVegGarden != null) {
                            myVegGarden.put(plant, quantity);
                            database.child("users").child(us.getUid()).child("myVegGarden").setValue(myVegGarden);
                        }

                        else {
                            myVegGarden = new HashMap<String, Integer>();
                            myVegGarden.put(plant, quantity);
                            database.child("users").child(us.getUid()).child("myVegGarden").setValue(myVegGarden);
                        }

                        dialog.dismiss();
                    }
                });

                builder.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    } });

                AlertDialog dialog = builder.create();
                dialog.show();
            }
        });

        return v;
    }

The adapter is the same as before.

I hope someone can help me.


Solution

  • Data retrieval is asynchronous in Firebase, so your list will not filled immediately. You may consider showing a ProgressBar or a simple TextView with a text similar to "Loading..." until data is retrieved.

    Also, the listener you provided to addValueEventListener() is called each time the data changes.

    For the second problem, I think you should do two tweaks. First one is updating the top addValueEventListener() in onCreateView():

    database.addValueEventListener(new ValueEventListener() {
                @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
    
                    User u = dataSnapshot.child("users").child(us.getUid()).getValue(User.class);
                    if (u != null) {
                        myVegGarden = u.getMyVegGarden();
                        if (myVegGarden != null) {
                            plantsList = new ArrayList<>();
                            quantityList = new ArrayList<>();
                            for(Map.Entry<String, Integer> entry : myVegGarden.entrySet()) {
                                plantsList.add(entry.getKey());
                                quantityList.add(entry.getValue());
                            }
                            adapter.notifyDataSetChanged();
                        }
                    }
                }
    
                @Override
                public void onCancelled(DatabaseError databaseError) {
                }
            });
    

    Second tweak is to simplify AlertDialog's onClick() as follows:

    @Override
    public void onClick(DialogInterface dialog, int which) {
        final String plant = options[plant_picker.getValue()];
        final int quantity = quantity_picker.getValue();
    
        // Update db value
        myVegGarden = new HashMap<>();
        myVegGarden.put(plant, quantity);
        database.child("users").child(us.getUid()).child("myVegGarden").setValue(myVegGarden);
    }
    

    This should suffice. Create a map with the plant and the quantity and setValue() will add it to the path if that pair doesn't exist there. If the pair exists in that path, setValue() will update it. This update will in turn calls the tweaked addValueEventListener(), updating plantList and quantityList.