Add SupportMapFragment (Android Google Maps) In RecyclerView

Aug 19, 2017
IllegalArgumentException: No view found for id 0x7f0d008b (*:id/map) for fragment SupportMapFragment

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

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.