Error
You might bump into the following error when adding SupportMapFragment
in RecyclerView
.
java.lang.IllegalArgumentException: No view found for id 0x7f0d008b (*:id/map) for fragment SupportMapFragment{d0d5bcf #1 id=0x7f0d008b}
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1293)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1528)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1595)
...
The error happend because the view ID R.id.map
is not attached to the RecyclerView/Activity yet during call to onBindViewHolder
. It is less likely to happen if SupportMapFragment
is in the first few RecyclerView items which are immediately visible when the Activity loads. It is most likely to happen when SupportMapFragment
is not immediately visible when the Activity loads and you have to scroll to reach the SupportMapFragment
item.
Error simulation
The call to replace R.id.map
layout with SupportMapFragment
on onBindViewHolder
might fail because R.id.map
might not attached to the RecyclerView/Activity yet.
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { ... @Override public void onBindViewHolder(ViewHolder holder, int position) { final MyItem item = items.get(position); // Assuming most of items' viewType is VIEW_TEXT, where there is one instance is VIEW_MAP if (item.viewType == MyItem.VIEW_TEXT) { holder.textView.setText(item.text); } else if (item.viewType == MyItem.VIEW_MAP) { SupportMapFragment mapFragment = holder.mapFragment; if (mapFragment == null) { mapFragment = SupportMapFragment.newInstance(); mapFragment.getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap googleMap) { LatLng latLng = item.position; googleMap.addMarker(new MarkerOptions().position(latLng)); googleMap.animateCamera(CameraUpdateFactory.newLatLng(latLng)); } }); } // for fragment // FragmentManager fragmentManager = getChildFragmentManager(); // for activity FragmentManager fragmentManager = getSupportFragmentManager(); // R.id.map is a FrameLayout, not a Fragment // ERROR: R.id.map might not be attached to RecyclerView/Activity yet fragmentManager.beginTransaction().replace(R.id.map, mapFragment).commit(); } } public class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; private SupportMapFragment mapFragment; public ViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.textView); } }}
Solution
To guaranteed R.id.map
is attached to the RecyclerView/Activity, we don't load and replace Fragment at onBindViewHolder
but do so at onViewAttachedToWindow
event.
If the error still occur unpredictably, you want want to remove the fragment at onViewDetachedFromWindow
.
I will demonstrate the solution with example of loading the RecyclerView with data.
public class ListActivity extends AppCompatActivity { private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { ... recyclerView = (RecyclerView) findViewById(R.id.list); // RecyclerView with have 2 different View Type: one to display text, another to load map List<MyItem> items = new ArrayList<>(); items.add(new MyItem("Item 1")); items.add(new MyItem("Item 2")); items.add(new MyItem("Item 3")); items.add(new MyItem(new LatLng(1.289545, 103.849972))); items.add(new MyItem("Item 4")); items.add(new MyItem("Item 5")); items.add(new MyItem("Item 6")); items.add(new MyItem("Item 7")); items.add(new MyItem("Item 8")); items.add(new MyItem("Item 9")); MyAdapter adapter = new MyAdapter(items); recyclerView.setAdapter(adapter); } class MyItem { public static final int VIEW_TEXT = 1; public static final int VIEW_MAP = 2; public final int viewType; public String text; public LatLng position; public MyItem(String value) { this.viewType = VIEW_TEXT; this.text = value; } public MyItem(LatLng value) { this.viewType = VIEW_MAP; this.position = value; } } class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private final List<MyItem> items; MyAdapter(List<MyItem> items) { this.items = items; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { int layoutId = R.layout.content_list_text; if (viewType == MyItem.VIEW_MAP) { layoutId = R.layout.content_list_map; } View inflatedView = LayoutInflater.from(parent.getContext()) .inflate(layoutId, parent, false); return new ViewHolder(inflatedView); } @Override public void onViewAttachedToWindow(ViewHolder holder) { super.onViewAttachedToWindow(holder); final MyItem item = holder.item; if (item != null && item.viewType == MyItem.VIEW_MAP) { holder.getMapFragmentAndCallback(new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap googleMap) { LatLng latLng = item.position; googleMap.addMarker(new MarkerOptions().position(latLng)); googleMap.animateCamera(CameraUpdateFactory.newLatLng(latLng)); } }); } } @Override public void onViewDetachedFromWindow(ViewHolder holder) { super.onViewDetachedFromWindow(holder); if (holder.item != null && holder.item.viewType == MyItem.VIEW_MAP) { // If error still occur unpredictably, it's best to remove fragment here // holder.removeMapFragment(); } } @Override public void onBindViewHolder(ViewHolder holder, int position) { final MyItem item = items.get(position); if (item.viewType == MyItem.VIEW_TEXT) { holder.textView.setText(item.text); } else if (item.viewType == MyItem.VIEW_MAP) { holder.item = item; } } @Override public int getItemCount() { return items.size(); } @Override public int getItemViewType(int position) { return items.get(position).viewType; } public class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; public FrameLayout mapLayout; private SupportMapFragment mapFragment; public MyItem item; public ViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.textView); mapLayout = (FrameLayout) itemView.findViewById(R.id.map); } public SupportMapFragment getMapFragmentAndCallback(OnMapReadyCallback callback) { if (mapFragment == null) { mapFragment = SupportMapFragment.newInstance(); mapFragment.getMapAsync(callback); } // for fragment // FragmentManager fragmentManager = getChildFragmentManager(); // for activity FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction().replace(R.id.map, mapFragment).commit(); return mapFragment; } public void removeMapFragment() { if (mapFragment != null) { FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction().remove(mapFragment).commitAllowingStateLoss(); mapFragment = null; } } } } }