使用Firebase的ListView和自定义适配器

问题描述:

在我的应用程序中,我有一个部分(片段),我想通过ListView显示植物及其数量(存储在Firebase上)。 通过使用浮动操作按钮,我允许更新与工厂相关的数量,如果它已经在数据库中,否则添加它。使用Firebase的ListView和自定义适配器

这里我的头两个问题:

#1 此片段显示在主要活动,因此在启动时,我想从数据库中检索到的数据被立即显示出来,但是这并未”不会发生,只有在添加/更新工厂后才会显示。

#2 这里是我的代码:

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; 
    } 
} 

嗯...数据库总是正确更新,但:有时plantsList和quantityLists插入副本,所以植物出现两次或三次,我不想要它。

编辑: 我成功地解决了这两个问题。 现在只是以下问题。相当经常发生这样的错误:

10-19 19:28:52.460 19148-19148/.veggarden E/AndroidRuntime:致命异常:主 工艺:.veggarden,PID:19148 显示java.lang.NullPointerException :尝试在 .veggarden.MyVegGardenAdapter上一个空 对象引用 调用虚拟方法 'java.lang.Object中 android.content.Context.getSystemService(java.lang.String中)'(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) atcom.android.internal.os.ZygoteInit.main(ZygoteInit。)。com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:866) com.android.internal.os.ZygoteInit.main(ZygoteInit。Java的:756)

直到现在出现此错误:

  1. 第一次我尝试添加植物时,我删除了我厂从DB(背景不为空,但加入后植物是)
  2. ,当我去到另一个片段通过底部的工具栏,然后我回到这个片段添加植物
  3. 添加许多植物时(很少和不那么肯定)

这里我了最新代码:

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; 
    } 

适配器是和以前一样。

我希望有人能帮助我。

+0

我解决了我的问题,也感谢你。现在只剩下一个问题,请看看,如果可以的话,我会很感激。 – Benny

数据检索在Firebase中是异步的,因此您的列表不会立即填充。您可以考虑显示一个ProgressBar或一个简单的TextView,其文本类似于“Loading ...”,直到检索到数据。

另外,您在每次数据更改时调用您提供给addValueEventListener()的听众。

对于第二个问题,我认为你应该做两个调整。第一个是在onCreateView()更新顶部addValueEventListener()

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) { 
      } 
     }); 

二的调整是为了简化AlertDialog的onClick()如下:

@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); 
} 

这应该足够了。使用工厂和数量创建地图,然后setValue()将其添加到路径中(如果该对不存在)。如果该对存在于该路径中,则setValue()将更新它。此更新将调用修改的addValueEventListener(),更新plantList和quantityList。

+0

我解决了将适配器放在数据库回调中的第一个问题,而我从你的想法开始解决了第二个问题。我用我的最后一个问题更新了我的帖子,请看看是否可以,谢谢你的帮助! – Benny

+0

哪一行是适配器中的第31行? – Mehmed

+0

对不起,我编辑过。无论如何它是:inflater =(LayoutInflater)this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); – Benny

这更像是一个问题而不是答案。 (它更容易在这里添加代码)。

在你onCreateView方法你有这样的:

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); 

但是,我不设置适配器plantListquantityList之前看到您填写个人的ArrayList?

+0

我从firebase中提取hashmap,然后用键和值填充plantsList和quantityList。在代码中你可以找到这个部分: for(Map.Entry entry:myVegGarden.entrySet())plantsList.add(entry.getKey()); quantityList.add(entry.getValue()); – Benny

+0

是的,但在设置适配器之前我没有看到你这样做。你在添加/更新之后做,但不是之前。 – Barns

+0

你说得对,我的第一个问题是由它引起的,谢谢! – Benny