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:
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.
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.