{"id":1880,"date":"2022-08-30T15:20:13","date_gmt":"2022-08-30T15:20:13","guid":{"rendered":"https:\/\/unknownerror.org\/index.php\/2013\/12\/02\/android-efficientadapter-with-two-different-views-collection-of-common-programming-errors\/"},"modified":"2022-08-30T15:20:13","modified_gmt":"2022-08-30T15:20:13","slug":"android-efficientadapter-with-two-different-views-collection-of-common-programming-errors","status":"publish","type":"post","link":"https:\/\/unknownerror.org\/index.php\/2022\/08\/30\/android-efficientadapter-with-two-different-views-collection-of-common-programming-errors\/","title":{"rendered":"Android: EfficientAdapter with two different Views-Collection of common programming errors"},"content":{"rendered":"<p>I&#8217;m using an extended version of BaseAdapter based on the EfficientAdapter example from the SDK demo samples.<\/p>\n<p>My data is basically an object (<code>ListPlaces<\/code>) which holds an <code>ArrayList<\/code> with the actual list of places, accessible via <code>listPlaces.getValues()<\/code>. This ArrayList data is sorted by range and the <code>ArrayList<\/code> consist of some special items (separators), with no data, but a <code>separator<\/code> flag set to <code>true<\/code>.<\/p>\n<p>Now whenever my <code>EfficientAdapter<\/code> gets a data object which is a separator it returns <code>false<\/code> for <code>public boolean isEnabled(int position)<\/code> and <code>public View getView(int position, View convertView, ViewGroup parent)<\/code> inflates two different layouts depending on if the current data object consists of real data or is just a separator dummy.<\/p>\n<p>This works great, if I inflate the layout every time. However, inflating the layout every time and calling <code>findViewById<\/code> makes the <code>ListView<\/code> almost unusabely slow.<\/p>\n<p>So I tried to use the EfficientAdapter with <code>ViewHolder<\/code> approach. But that didn&#8217;t work right out of the box, because of the two different views I try to access. So whenever my <code>convertView != null<\/code> (the else-case) accesses the items on the layout via our <code>ViewHolder<\/code> and when the previous View was a separator it of course doesn&#8217;t work to access a TextView there which is only available on the &#8220;real&#8221; items layout.<\/p>\n<p>So I also force my <code>getView()<\/code> to inflate the layout not only when <code>convertView == null<\/code>, but also when the previous listRow is different than the current one: <code>if (convertView == null || (listRow != listRow_previous)) { [....] }<\/code><\/p>\n<p>This seems to almost work now. Or at least it doesn&#8217;t crash right from the beginning. But it still crashes and I don&#8217;t know what I&#8217;ve to do different. I&#8217;ve tried to look into <code>convertView.getID()<\/code> and <code>convertView.getResources()<\/code>, but that wasn&#8217;t really helpful so far. Maybe someone else has an idea how I can check whether my current <code>convertView<\/code> matches with the list item layout or the list separator layout. Thanks.<\/p>\n<p>Here&#8217;s the code. Where ever there is a [&#8230;] I took out some less important code to make it easier to read and understand:<\/p>\n<pre><code>private class EfficientAdapter extends BaseAdapter {\n  private LayoutInflater mInflater;\n  private ListPlaces listPlaces;\n\n  private ListRow listRow;\n  private ListRow listRow_previous;\n\n\n  public EfficientAdapter(Context context, ListPlaces listPlaces) {\n      \/\/ Cache the LayoutInflate to avoid asking for a new one each time.\n      mInflater = LayoutInflater.from(context);\n\n      \/\/ Data\n      this.listPlaces = listPlaces;\n  }\n\n  \/**\n    * The number of items in the list is determined by the number of items\n    * in our ArrayList\n    *\n    * @see android.widget.ListAdapter#getCount()\n    *\/\n  public int getCount() {\n      return listPlaces.getValues().size();\n  }\n\n  \/**\n    * Since the data comes from an array, just returning the index is\n    * sufficent to get at the data. If we were using a more complex data\n    * structure, we would return whatever object represents one row in the\n    * list.\n    *\n    * @see android.widget.ListAdapter#getItem(int)\n    *\/\n  public Object getItem(int position) {\n      return position;\n  }\n\n  \/**\n    * Use the array index as a unique id.\n    *\n    * @see android.widget.ListAdapter#getItemId(int)\n    *\/\n  public long getItemId(int position) {\n      return position;\n  }\n\n  @Override\n  public  boolean isEnabled(int position) {\n      \/\/ return false if item is a separator:\n      if(listPlaces.getValues().get(position).separator &gt;= 0)\n          return false;\n      else\n          return true;\n  }\n\n  @Override\n  public boolean  areAllItemsEnabled() {\n      return false;                     \n  }\n\n\n\n  \/**\n    * Make a view to hold each row.\n    *\n    * @see android.widget.ListAdapter#getView(int, android.view.View,\n    *      android.view.ViewGroup)\n    *\/\n  public View getView(int position, View convertView, ViewGroup parent) {\n\n      \/\/ Get the values for the current list element\n      ListPlacesValues curValues = listPlaces.getValues().get(position);\n      if (curValues.separator &gt;= 0) \n          listRow = ListRow.SEPARATOR;\n      else\n          listRow = ListRow.ITEM;\n      Log.i(TAG,\"Adapter: getView(\"+position+\") \" + listRow + \" (\" + listRow_previous + \") -&gt; START\");\n\n      \/\/ A ViewHolder keeps references to children views to avoid unneccessary calls\n      \/\/ to findViewById() on each row.\n      ViewHolder holder;\n\n      \/\/ When convertView is not null, we can reuse it directly, there is no need\n      \/\/ to reinflate it. We only inflate a new View when the convertView supplied\n      \/\/ by ListView is null.\n      if (convertView == null || (listRow != listRow_previous)) {\n          Log.i(TAG, \"--&gt; (convertView == null) at position: \" + position);\n          \/\/ Creates a ViewHolder and store references to the two children views\n          \/\/ we want to bind data to.\n          holder = new ViewHolder();\n\n          if (listRow == ListRow.SEPARATOR) {\n                  convertView = mInflater.inflate(R.layout.taxonomy_list_separator, null);\n                  holder.separatorText = (TextView) convertView.findViewById(R.id.separatorText);\n                  convertView.setTag(holder);\n                  Log.i(TAG,\"\\tCREATE SEPARATOR: convertView ID: \" + convertView.getId() + \" Resource: \" + convertView.getResources());\n\n          }\n          else {\n\n                  convertView = mInflater.inflate(R.layout.taxonomy_listitem, null);\n                  holder.name = (TextView) convertView.findViewById(R.id.name);\n                  holder.category = (TextView) convertView.findViewById(R.id.category);\n                  \/\/ [...]\n\n                  convertView.setTag(holder);\n\n                  Log.i(TAG,\"\\tCREATE ITEM: convertView ID: \" + convertView.getId() + \" Resource: \" + convertView.getResources());\n          }\n      } else {\n          \/\/ Get the ViewHolder back to get fast access to the TextView\n          \/\/ and the ImageView.\n          Log.i(TAG,\"\\tconvertView ID: \" + convertView.getId() + \" Resource: \" + convertView.getResources());\n\n          holder = (ViewHolder) convertView.getTag();\n          convertView.setAnimation(null);\n      }\n\n      \/* Bind the data efficiently with the holder *\/\n      if (listRow == ListRow.SEPARATOR) {\n          String separatorText;\n          switch (curValues.separator) {\n          case 0: separatorText=\"case 0\"; break;\n          case 1: separatorText=\"case 1\"; break;\n          case 2: separatorText=\"case 2\"; break;\n          \/\/ [...]\n        default: separatorText=\"[ERROR]\"; break;\n          }\n          holder.separatorText.setText(separatorText);\n      } \n      else {\n          \/\/ Set the name:\n          holder.name.setText(curValues.name);\n          \/\/ Set category\n          String cat = curValues.classification.toString();\n          cat = cat.substring(1,cat.length()-1);        \/\/ removing \"[\" and \"]\"\n          if (cat.length() &gt; 35) {\n                  cat = cat.substring(0, 35);\n                  cat = cat + \"...\";\n          }\n          holder.category.setText(cat);\n\n          \/\/ [...] (and many more TextViews and ImageViews to be set)\n\n      }\n\n      listRow_previous = listRow;\n      Log.i(TAG,\"Adapter: getView(\"+position+\") -&gt; DONE\");\n      return convertView;\n  }\n\n  private class ViewHolder {\n      TextView name;\n      TextView category;\n      \/\/ [...] -&gt; many more TextViews and ImageViews\n\n      TextView separatorText;\n  }\n}\n<\/code><\/pre>\n<p>And here my <strong>Logcat<\/strong> output:<\/p>\n<pre><code>  755     ListPlaces_Activity  I  onPostExecute: notifyDataSetChanged()                                                                                                \n  755     ListPlaces_Activity  I  Adapter: getView(0) SEPARATOR (null) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 0                                                                                             \n  755     ListPlaces_Activity  I        CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                  \n  755     ListPlaces_Activity  I  Adapter: getView(0) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(1) ITEM (SEPARATOR) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 1                                                                                             \n  755     ListPlaces_Activity  I        CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                       \n  755     ListPlaces_Activity  I  Adapter: getView(1) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(2) SEPARATOR (ITEM) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 2                                                                                             \n  755     ListPlaces_Activity  I        CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                  \n  755     ListPlaces_Activity  I  Adapter: getView(2) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(3) ITEM (SEPARATOR) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 3                                                                                             \n  755     ListPlaces_Activity  I        CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                       \n  755     ListPlaces_Activity  I  Adapter: getView(3) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(4) ITEM (ITEM) -&gt; START                                                                                             \n  755     ListPlaces_Activity  I        convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                                    \n  755     ListPlaces_Activity  I  Adapter: getView(4) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(5) ITEM (ITEM) -&gt; START                                                                                             \n  755     ListPlaces_Activity  I        convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                                    \n  755     ListPlaces_Activity  I  Adapter: getView(5) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(6) ITEM (ITEM) -&gt; START                                                                                             \n  755     ListPlaces_Activity  I        convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                                    \n  755     ListPlaces_Activity  I  Adapter: getView(6) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(0) SEPARATOR (ITEM) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 0                                                                                             \n  755     ListPlaces_Activity  I        CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                  \n  755     ListPlaces_Activity  I  Adapter: getView(0) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(1) ITEM (SEPARATOR) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 1                                                                                             \n  755     ListPlaces_Activity  I        CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                       \n  755     ListPlaces_Activity  I  Adapter: getView(1) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(2) SEPARATOR (ITEM) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 2                                                                                             \n  755     ListPlaces_Activity  I        CREATE SEPARATOR: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                  \n  755     ListPlaces_Activity  I  Adapter: getView(2) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(3) ITEM (SEPARATOR) -&gt; START                                                                                        \n  755     ListPlaces_Activity  I  --&gt; (convertView == null) at position: 3                                                                                             \n  755     ListPlaces_Activity  I        CREATE ITEM: convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                       \n  755     ListPlaces_Activity  I  Adapter: getView(3) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(4) ITEM (ITEM) -&gt; START                                                                                             \n  755     ListPlaces_Activity  I        convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                                    \n  755     ListPlaces_Activity  I  Adapter: getView(4) -&gt; DONE                                                                                                          \n  755     ListPlaces_Activity  I  Adapter: getView(5) ITEM (ITEM) -&gt; START                                                                                             \n  755     ListPlaces_Activity  I        convertView ID: 2131296317 Resource: android.content.res.Resources@437613e0                                                    \n  755          AndroidRuntime  D  Shutting down VM                                                                                                                     \n  755                dalvikvm  W  threadid=3: thread exiting with uncaught exception (group=0x4001aa28)                                                                \n  755          AndroidRuntime  E  Uncaught handler: thread main exiting due to uncaught exception                                                                      \n  755          AndroidRuntime  E  java.lang.NullPointerException                                                                                                       \n  755          AndroidRuntime  E        at com.tato.main.ListPlaces_Activity$EfficientAdapter.getView(ListPlaces_Activity.java:330)                                    \n  755          AndroidRuntime  E        at android.widget.HeaderViewListAdapter.getView(HeaderViewListAdapter.java:191)                                                \n  755          AndroidRuntime  E        at android.widget.AbsListView.obtainView(AbsListView.java:1255)                                                                \n  755          AndroidRuntime  E        at android.widget.ListView.makeAndAddView(ListView.java:1658)                                                                  \n  755          AndroidRuntime  E        at android.widget.ListView.fillDown(ListView.java:637)                                                                         \n  755          AndroidRuntime  E        at android.widget.ListView.fillFromTop(ListView.java:694)                                                                      \n  755          AndroidRuntime  E        at android.widget.ListView.layoutChildren(ListView.java:1502)                                                                  \n  755          AndroidRuntime  E        at android.widget.AbsListView.onLayout(AbsListView.java:1112)                                                                  \n  755          AndroidRuntime  E        at android.view.View.layout(View.java:6569)                                                                                    \n  755          AndroidRuntime  E        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)                                                           \n  755          AndroidRuntime  E        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:998)                                                           \n  755          AndroidRuntime  E        at android.widget.LinearLayout.onLayout(LinearLayout.java:918)                                                                 \n  755          AndroidRuntime  E        at android.view.View.layout(View.java:6569)                                                                                    \n  755          AndroidRuntime  E        at android.widget.FrameLayout.onLayout(FrameLayout.java:333)                                                                   \n  755          AndroidRuntime  E        at android.view.View.layout(View.java:6569)                                                                                    \n  755          AndroidRuntime  E        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)                                                           \n  755          AndroidRuntime  E        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:998)                                                           \n  755          AndroidRuntime  E        at android.widget.LinearLayout.onLayout(LinearLayout.java:918)                                                                 \n  755          AndroidRuntime  E        at android.view.View.layout(View.java:6569)                                                                                    \n  755          AndroidRuntime  E        at android.widget.FrameLayout.onLayout(FrameLayout.java:333)                                                                   \n  755          AndroidRuntime  E        at android.view.View.layout(View.java:6569)                                                                                    \n  755          AndroidRuntime  E        at android.view.ViewRoot.performTraversals(ViewRoot.java:979)                                                                  \n  755          AndroidRuntime  E        at android.view.ViewRoot.handleMessage(ViewRoot.java:1613)                                                                     \n  755          AndroidRuntime  E        at android.os.Handler.dispatchMessage(Handler.java:99)                                                                         \n  755          AndroidRuntime  E        at android.os.Looper.loop(Looper.java:123)                                                                                     \n  755          AndroidRuntime  E        at android.app.ActivityThread.main(ActivityThread.java:4203)                                                                   \n  755          AndroidRuntime  E        at java.lang.reflect.Method.invokeNative(Native Method)                                                                        \n  755          AndroidRuntime  E        at java.lang.reflect.Method.invoke(Method.java:521)                                                                            \n  755          AndroidRuntime  E        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)                                             \n  755          AndroidRuntime  E        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549)                                                                \n  755          AndroidRuntime  E        at dalvik.system.NativeStart.main(Native Method)\n<\/code><\/pre>\n<ol>\n<li>\n<p>You forgot a couple of methods you need to override: getViewTypeCount() and getItemViewType(). These are not needed for lists where all rows are the same, but they are very important for your scenario. Implement these properly, and Android will maintain separate object pools for your headers and detail rows.<\/p>\n<p>Or, you could look at:<\/p>\n<\/li>\n<li>\n<p>Thanks to the hint with getViewTypeCount() and getItemViewType() it works perfectly now.<\/p>\n<p>Implementing these two methods was very simple:<\/p>\n<pre><code>@Override\npublic int getViewTypeCount() {\n    return 2;\n}\n\n@Override\npublic int getItemViewType(int position) {\nif(listPlaces.getValues().get(position).separator &gt;= 0)\n    return 0;\nelse\n    return 1;\n}\n<\/code><\/pre>\n<p>As <em>commonsware<\/em> mentioned in his answer this way Android will maintain different object pools for different list items, which also means you can remove the check for <code>listRow_previous<\/code> in my example and change the <code>if (convertView == null || (listRow != listRow_previous))<\/code> to <code>if (convertView == null)<\/code> only.<\/p>\n<\/li>\n<\/ol>\n<p id=\"rop\"><small>Originally posted 2013-12-02 21:10:52. <\/small><\/p>","protected":false},"excerpt":{"rendered":"<p>I&#8217;m using an extended version of BaseAdapter based on the EfficientAdapter example from the SDK demo samples. My data is basically an object (ListPlaces) which holds an ArrayList with the actual list of places, accessible via listPlaces.getValues(). This ArrayList data is sorted by range and the ArrayList consist of some special items (separators), with no [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1880","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/posts\/1880","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/comments?post=1880"}],"version-history":[{"count":0,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/posts\/1880\/revisions"}],"wp:attachment":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/media?parent=1880"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/categories?post=1880"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/tags?post=1880"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}