55

My app shows a map and i want that users can't swipe over a certain region. So i'm trying to add bounds but it makes the app to crash. Here is the working code:

public class MapViewer extends Activity implements OnInfoWindowClickListener {
    private LatLng defaultLatLng = new LatLng(42.564241, 12.22759);
    private GoogleMap map;
    private int zoomLevel = 5;
    private Database db = new Database(this);

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mapviewer);

        try {
            map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
            if (map != null) {
                map.setMyLocationEnabled(true);
                map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
                map.getUiSettings().setRotateGesturesEnabled(false);

                map.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultLatLng, zoomLevel));

                this.addMerchantMarkers(new MarkerOptions());

                map.setOnInfoWindowClickListener(this);
            }
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPause() {
        if (map != null) {
            map.setMyLocationEnabled(false);
            map.setTrafficEnabled(false);
        }
        super.onPause();
    }

    public void addMerchantMarkers(MarkerOptions mo) {
        SQLiteDatabase dbRead = db.getReadableDatabase();
        String[] columns = {"title", "addr", "lat", "lon"};
        Cursor result = dbRead.query("merchants", columns, null, null, null, null, null);

        while(result.moveToNext()) {
            String merchant = result.getString(0);
            String address = result.getString(1);
            float lat = result.getFloat(2);
            float lon = result.getFloat(3);

            LatLng pos = new LatLng(lat, lon);

            map.addMarker(mo.position(pos)
                    .title(merchant)
                    .snippet(address)
                    .icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_50)));;
        }
    }
}

And this is the code i add in onCreate method that cause the crash:

    LatLngBounds.Builder builder = new LatLngBounds.Builder();
    builder.include(new LatLng(47.09194444, 18.52166666));
    builder.include(new LatLng(36.448311, 6.62555555));
    LatLngBounds bounds = builder.build();

    CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, 30);

    map.animateCamera(cu);

Here is the LogCat:

08-10 20:59:41.689: E/AndroidRuntime(6304): FATAL EXCEPTION: main
08-10 20:59:41.689: E/AndroidRuntime(6304): Process: com.example.myapp, PID: 6304
08-10 20:59:41.689: E/AndroidRuntime(6304): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.myapp/com.example.myapp.MapViewer}: java.lang.IllegalStateException: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view.  Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions.
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2215)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2264)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.access$800(ActivityThread.java:144)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1205)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.os.Handler.dispatchMessage(Handler.java:102)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.os.Looper.loop(Looper.java:136)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.main(ActivityThread.java:5139)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at java.lang.reflect.Method.invokeNative(Native Method)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at java.lang.reflect.Method.invoke(Method.java:515)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:796)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:612)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at dalvik.system.NativeStart.main(Native Method)
08-10 20:59:41.689: E/AndroidRuntime(6304): Caused by: java.lang.IllegalStateException: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view.  Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions.
08-10 20:59:41.689: E/AndroidRuntime(6304):     at mut.b(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at oxp.a(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at oxi.a(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at oyf.b(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at grl.onTransact(SourceFile:92)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.os.Binder.transact(Binder.java:361)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.google.android.gms.maps.internal.IGoogleMapDelegate$a$a.animateCamera(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.google.android.gms.maps.GoogleMap.animateCamera(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.example.myapp.MapViewer.onCreate(MapViewer.java:59)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.Activity.performCreate(Activity.java:5231)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2169)
08-10 20:59:41.689: E/AndroidRuntime(6304):     ... 11 more
Raghunandan
  • 132,755
  • 26
  • 225
  • 256
smartmouse
  • 13,912
  • 34
  • 100
  • 166
  • Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions. Looks like your map is not ready. Also check for google play services availability. – Raghunandan Aug 10 '14 at 19:03
  • I used newLatLngBounds(bounds, 30, 10, 10) and it doesn't crash, but the resulting map does not reflects the bounds i set. In addition i don't know how to use those 3 int values :( – smartmouse Aug 10 '14 at 19:06
  • What is `MapViewer.java` line 59. – Raghunandan Aug 10 '14 at 19:09
  • At line 59 there is map.animateCamera(cu); – smartmouse Aug 10 '14 at 19:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59062/discussion-between-raghunandan-and-smartmouse). – Raghunandan Aug 10 '14 at 19:26
  • @smartmouse mark a correct answer based on your problem or the community votes. – ahmadalibaloch Oct 04 '17 at 07:55

10 Answers10

102

You can use MapLoadedCallBack;

map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
  @Override
  public void onMapLoaded() {
      map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
  }
});

and also you can use this event that occurs prier to above.

  map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
        @Override
        public void onCameraChange(CameraPosition arg0) {
        map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
  }
});
ahmadalibaloch
  • 5,851
  • 2
  • 50
  • 59
  • 1
    We unfortunately still get this crash even when we put `moveCamera` in `onMapLoaded`. I think the map is loaded but on some devices, it may not be fully layouted. So putting `handler.post` inside `onMapLoaded` helped with it a lot. But we are still seeing the crash even though it is lower. – tasomaniac Oct 19 '16 at 13:32
  • 1
    This works for me but I am able to see map loaded at the default location, and then after a slight delay it is moved to desired position. Visually it looks much better when I use addOnGlobalLayoutListener instead. – Singed Apr 23 '19 at 15:10
64

You can keep the error from occurring by following the advice from the log:

"...use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions."

The extra parameters are the width and height of your screen. Here's how your updated code would look:

LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(new LatLng(47.09194444, 18.52166666));
builder.include(new LatLng(36.448311, 6.62555555));
LatLngBounds bounds = builder.build();

// begin new code:
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
int padding = (int) (width * 0.12); // offset from edges of the map 12% of screen

CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding);
// end of new code

map.animateCamera(cu);
Patrick Coffey
  • 741
  • 7
  • 7
  • 1
    Was looking for a solution that can work on Xamarin.Android, and this worked perfectly. Plus, the idea of using a percentage of the screen width so the padding is the same no matter what screen size we're targeting is a very good improvement to my previous code. With much thanks, Patrick ! – YumeYume May 26 '16 at 13:15
  • 1
    this was the only possible way for me.. All other solutions didn't work on some devices. – Stephan Huewe Nov 02 '16 at 06:04
  • Thanks. Will `animateCamera` or `moveCamera` work without exception before a layout has been shown? – CoolMind Mar 05 '19 at 07:47
  • The question is why do you think the map covers all the screen!? It may not, right? `int width = getResources().getDisplayMetrics().widthPixels;` this means you're getting screen dimension as layout for Map but map could cover, say 10 percent of the screen and it may be aligned to the upper-right corner of the screen, not to the center with `int padding = (int) (width * 0.12);` 12% padding – Farid Jul 16 '19 at 14:33
7

Documentation for onMapReady() clearly states that you have to wait for both onMapReadyCallback and ViewTreeObserver.OnGlobalLayoutListener:

Note that this does not guarantee that the map has undergone layout. Therefore, the map's size may not have been determined by the time the callback method is called. If you need to know the dimensions or call a method in the API that needs to know the dimensions, get the map's View and register an ViewTreeObserver.OnGlobalLayoutListener as well.

It is quite inconvenient, but one way of doing it would be by using RxJava. You could emit two observables, first one in onMapReady and second one in onGlobalLayout, then zip them together and do whatever you need to do. This way you can be certain that both callbacks were fired.

ElDae
  • 173
  • 1
  • 5
  • 1
    Maybe this would work? https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/OnMapAndViewReadyListener.java – Sebastian Gallese Nov 17 '17 at 21:56
4

Caused by: java.lang.IllegalStateException: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions.

From the docs https://developer.android.com/reference/com/google/android/gms/maps/CameraUpdateFactory.html#newLatLngBounds(com.google.android.gms.maps.model.LatLngBounds, int)

Do not change the camera with this camera update until the map has undergone layout (in order for this method to correctly determine the appropriate bounding box and zoom level, the map must have a size). Otherwise an IllegalStateException will be thrown. It is NOT sufficient for the map to be available (i.e. getMap() returns a non-null object); the view containing the map must have also undergone layout such that its dimensions have been determined. If you cannot be sure that this has occured, use newLatLngBounds(LatLngBounds, int, int, int) instead and provide the dimensions of the map manually.

Note: getMap() could return null. It is better to check for Availability of Google play services before initialize GoogleMap object.

Raghunandan
  • 132,755
  • 26
  • 225
  • 256
4
Caused by: java.lang.IllegalStateException: Map size should not be 0. Most likely, layout has not yet occured for the map view.

The reason Behind this error is because the Map layout has not been completed, You should implement OnMapReadyCallback in your calls, which will ensure that your code will only run after your layout is completed or Implement the below callback

map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
    @Override
    public void onCameraChange(CameraPosition arg0) {
    map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
}
});

Duplicate Links

moveCamera with CameraUpdateFactory.newLatLngBounds crashes

IllegalStateException map size should not be 0

I don't if there is a way to merge this link. I Hope i helped

Community
  • 1
  • 1
Bikash
  • 1,452
  • 1
  • 15
  • 24
  • "You should implement OnMapReadyCallback in your calls, which will ensure that your code will only run after your layout is completed or Implement the below callback", please don't add extra misleading lines to your answer. This has nothing to do with the layout being ready. It just listens to camera movement which could be a user interaction as well. – Farid Oct 29 '20 at 08:31
4

This worked for me:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    mMapFragment.getMapAsync(this);
}

@Override
public void onMapReady(GoogleMap googleMap) {
    mMapFragment.getView().getViewTreeObserver().addOnGlobalLayoutListener(this);
}

@Override
public void onGlobalLayout() {
    mMapFragment.getView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
    //Only after onMapReady & onGlobalLayout newLatLngBounds will be available
    initMapPosition(); //newLatLngBounds here
}
Evgeny Nozdrev
  • 1,530
  • 12
  • 15
  • Sometimes i caught an exception `com.google.maps.api.android.lib6.common.apiexception.c: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions.` – Vlad Apr 02 '20 at 06:39
3

I've created a way to combine the two callbacks: onMapReady and onGlobalLayout into one single observable which will emit only when both the events have been triggered.

https://gist.github.com/abhaysood/e275b3d0937f297980d14b439a8e0d4a

public final class OnMapAndLayoutReady {

private OnMapAndLayoutReady() {
}

/**
 * Converts {@link OnMapReadyCallback} to an observable.
 * Note that this method calls {@link MapView#getMapAsync(OnMapReadyCallback)} so you there is no
 * need to initialize google map view manually.
 */
private static Observable<GoogleMap> loadMapObservable(final MapView mapView) {
    return Observable.create(new Observable.OnSubscribe<GoogleMap>() {
        @Override
        public void call(final Subscriber<? super GoogleMap> subscriber) {
            OnMapReadyCallback mapReadyCallback = new OnMapReadyCallback() {
                @Override
                public void onMapReady(GoogleMap googleMap) {
                    subscriber.onNext(googleMap);
                }
            };
            mapView.getMapAsync(mapReadyCallback);
        }
    });
}

/**
 * Converts {@link ViewTreeObserver.OnGlobalLayoutListener} to an observable.
 * This methods also takes care of removing the global layout listener from the view.
 */
private static Observable<MapView> globalLayoutObservable(final MapView view) {
    return Observable.create(new Observable.OnSubscribe<MapView>() {
        @Override
        public void call(final Subscriber<? super MapView> subscriber) {
            final ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    subscriber.onNext(view);
                }
            };
            view.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
        }
    });
}

/**
 * Takes {@link #globalLayoutObservable(MapView)} and {@link #loadMapObservable(MapView)} and zips their result.
 * This means that the subscriber will only be notified when both the observables have emitted.
 */
public static Observable<GoogleMap> onMapAndLayoutReadyObservable(final MapView mapView) {
    return Observable.zip(globalLayoutObservable(mapView), loadMapObservable(mapView), new Func2<MapView, GoogleMap, GoogleMap>() {
        @Override
        public GoogleMap call(MapView mapView, GoogleMap googleMap) {
            return googleMap;
        }
    });
}
}
Abhay Sood
  • 480
  • 2
  • 6
  • 20
2

Place the call within a runnable and post it to the views handler, like so:

    mapFragment.getView().post(new Runnable() {
        @Override
        public void run() {
            map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), CAMERA_PADDING));
        }
    });

When the view has been laid out the runnable will execute and correctly position the map. The issue with ahmadalibalochs answer, is that you will see a flash of the globe before it correctly positions itself. Posting it to the view handler, resolves this issue.

McP
  • 813
  • 1
  • 8
  • 16
  • "mapFragment.getView().post" nope, that doesn't mean "When the view has been laid out the runnable will execute". "mapFragment.getView().post" means anything inside `Runnable` will be executed in UI thread and doesn't guarantee view being laid out – Farid Oct 29 '20 at 08:36
1

The answer that is marked correct is not a 100% correct. I think that can still fail in cases where the map never loads due to connectivity, or if the map gets updated so quickly that it never fully finishes loading. I have used the addOnGlobalLayoutListener.

ConstraintLayout mapLayout = (ConstraintLayout)findViewById(R.id.activityLayout);
mapLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
    @Override
    public void onGlobalLayout()
    {
         //Your map code
    }
});
Siddharth
  • 31
  • 5
1

In Kotlin, this is what worked for me.

The following code, sometimes threw me the exception:

override fun onMapReady(googleMap: GoogleMap) {
    mMap = googleMap
    //mMap.uiSettings.isZoomControlsEnabled = true
    mMap?.uiSettings?.isMyLocationButtonEnabled = true
    mMap?.isMyLocationEnabled = true
    val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, padding)
    mMap?.animateCamera(cameraUpdate) 
}

Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions

This code prevented the error:

override fun onMapReady(googleMap: GoogleMap) {
    mMap = googleMap
    //mMap.uiSettings.isZoomControlsEnabled = true
    mMap?.uiSettings?.isMyLocationButtonEnabled = true
    mMap?.isMyLocationEnabled = true
    mMap?.setOnMapLoadedCallback {
        val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, padding)
        mMap?.animateCamera(cameraUpdate)
    }
}

By waiting on the onMapLoadedCallback before running my CameraUpdate function

JoeGalind
  • 3,545
  • 2
  • 29
  • 33