I'm using the new snapshot() method on the GoogleMap object, and in my SnapshotReadyCallback I'm swapping out the MapView in my layout for an ImageView with the Bitmap passed to me from the callback. The unfortunate thing about this is, even though I'm getting a Bitmap it appears that the snapshot was taken before the map finished rendering.
I should also add that this View is being created programmatically, and isn't added to the Activity right away (it's actually being placed in a ScrollView with a bunch of other Views).
The gist of my code (for brevity's sake) is:
public class MyCustomView extends LinearLayout {
private FrameLayout mapContainer;
#Override public void onAttachedToWindow() {
super.onAttachedToWindow();
// Setup GoogleMapOptions ...
mapView = new MapView(getContext(), googleMapOptions);
mapContainer.addView(mapView);
mapView.onCreate(null);
mapView.onResume();
// Setup CameraUpdate ...
mapView.getViewTreeObserver().addOnGlobalLayoutListener(new MapLayoutListener(mapView, cameraUpdate));
}
private class MapLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private final WeakReference<MapView> mapViewReference;
private final CameraUpdate cameraUpdate;
public MapLayoutListener(MapView mapView, CameraUpdate cameraUpdate) {
mapViewReference = new WeakReference<MapView>(mapView);
this.cameraUpdate = cameraUpdate
}
#Override public void onGlobalLayout() {
MapView mapView = mapViewReference.get();
if (mapView == null) {
return;
}
mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
GoogleMap map = mapView.getMap();
if (map == null) {
return;
}
try {
map.moveCamera(cameraUpdate);
} catch (IllegalStateException e) {
e.printStackTrace();
}
map.snapshot(new GoogleMap.SnapshotReadyCallback() {
#Override public void onSnapshotReady(Bitmap bitmap) {
MapView mapView = mapViewReference.get();
if (mapView == null) {
return;
}
mapContainer.removeAllViews();
ImageView imageView = new ImageView(getContext());
imageView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
imageView.setImageBitmap(bitmap);
mapContainer.addView(imageView);
}
});
}
}
}
Sometimes an IllegalStateException is thrown when map.moveCamera(cameraUpdate) is called, complaining that the View has not been laid out yet, but I'm calling it inside the OnGlobalLayoutListener which I thought was the proper way to get a callback when your View is all done rendering/laying out etc. This exception makes me think otherwise.
So what's the proper way to do this? The docs are scant on details, and the View "lifecycle" isn't very clear. What I need is a callback when the View is ready for me to call snapshot()
Related
I'm using the play services v9.8.0 (without location service permissions) and am still facing a leak when I use a MapView in a dialog fragment. I'm using it like in my code example and I use it to display a location only and I DON'T have setMyLocationEnabled (as I don't even have the permissions for this setting).
Does anyone see a problem in my code? I'm getting a leak like the one here: MapView v2 keeping Context around. I do following:
create a dialog
replace a view in my layout with a MapView (because I allow to use static maps as well, so my default view is a ImageView in my layout, that will be replaced with a MapView)
Then it happens that my fragments leaks MapView.mContext...
Code - Dialog Fragment
public class DialogMediaDetails extends DialogFragment
{
private GoogleMap mGoogleMap = null;
private MapView mMapView = null;
#Override
public final Dialog onCreateDialog(Bundle savedInstanceState)
{
Dialog dlg = ...; // create dialog
View view = ...; // get view from dialog
Location location = ...; // defined location
initGoogleMap();
return dlg;
}
private void initGoogleMap(Location location)
{
mMapView = new MapView(getActivity());
MapsInitializer.initialize(getActivity());
// Updates the location and zoom of the MapView
mMapView.onCreate(null);
mMapView.getMapAsync(new OnMapReadyCallback()
{
#Override
public void onMapReady(GoogleMap googleMap)
{
LatLng coordinates = new LatLng(location.getLatitude(), location.getLongitude());
googleMap.addMarker(new MarkerOptions().position(coordinates));
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15));
mGoogleMap = googleMap;
mMapView.onResume();
}
});
mMapView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
mMapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// MapView is scrollable, so we disable dragging
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return false;
}
});
}
});
replaceHeader(mMapView);
}
private void replaceHeader(View view)
{
ViewGroup parent = (ViewGroup) pbHeader.getParent();
int index = parent.indexOfChild(pbHeader);
ViewGroup.LayoutParams lp = pbHeader.getLayoutParams();
parent.removeView(pbHeader);
parent.addView(view, index, lp);
}
// ----------------------------------------
// forward all lifecycle events to MapView
// ----------------------------------------
#Override
public void onResume() {
super.onResume();
if (mMapView != null)
mMapView.onResume();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mMapView != null)
mMapView.onSaveInstanceState(outState);
}
#Override
public void onPause() {
super.onPause();
if (mMapView != null)
mMapView.onPause();
}
#Override
public void onLowMemory() {
super.onLowMemory();
if (mMapView != null)
mMapView.onLowMemory();
}
#Override
public void onDestroy() {
super.onDestroy();
if (mMapView != null)
mMapView.onDestroy();
mMapView = null;
}
}
There are some issues reported about leaks with MapView. You may try calling googleMap.setMyLocationEnabled(false); in onDestroy to prevent the leak from happening. Failure to call either MapView.onDestroy or GoogleMap.setMyLocationEnabled(false) will cause a leak. Here's a related thread which might help.
I had the same issue, and realized in my heap reference tree that the bug is coming from the lite mode part of the library. Try not using lite mode and adding all the corresponding life cycles for mapview.
I'm using Google Maps Android API Utility Library and I'm downloading certain images from internet that I want to use as markers.
The way I'm doing it is like in the following snippet:
class MarkerItemClusterRenderer extends DefaultClusterRenderer<MarkerItem> {
...
#Override
protected void onBeforeClusterItemRendered(MarkerItem item,
final MarkerOptions markerOptions) {
super.onBeforeClusterItemRendered(item, markerOptions);
mImageLoader.get(item.getImageUrl(), new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.i("XXX", error.toString());
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response != null && response.getBitmap() != null) {
mImageIcon.setImageBitmap(response.getBitmap());
Bitmap icon = mIconGenerator.makeIcon();
Bitmap bhalfsize = Bitmap.createScaledBitmap(icon, 150,
150, false);
markerOptions.icon(BitmapDescriptorFactory
.fromBitmap(bhalfsize));
}
}
});
}
The problem is, that when the image is downloaded, the map (and thus the marker) doesn't refresh, so most of the times (but not always) I still see the red default markers.
I tried to do mImageIcon.invalidate(); mImageIcon.requestLayout(); but there's still no luck.
Is there anyway to achieve this?
Thanks a lot in advance.
You just need to make all this stuff in
protected void onClusterItemRendered(T clusterItem, Marker marker) {
...
}
In onBeforeClusterItemRendered you set icon on MarkerOptions in async callback. At this time it could be added to map and become real Marker. So you icon will be set to already useless object.
That's why you need to do it in onClusterItemRendered
Let's say you have GoogleMap object declared as:
private GoogleMap mMap;
In onResponse() method before applying any change to marker, try writing following statement to clear previous markers:
mMap.clear();
Now set your new marker.
I might be a bit late but i write it down so it can be useful for somebody looking for a solution like i was.
Basically what you have to do is refresh the marker and not the ClusterItem, but i used my own ClusterItem implementation to store some important data.
So your code inside onBeforeClusterItemRendered becomes like this:
LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds; //take visible region on map
if(bounds.contains(item.getPosition()) && !item.hasImage()) { //if item is not inside that region or it has an image already don't load his image
mImageLoader.get(item.getImageUrl(), new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.i("XXX", error.toString());
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response != null && response.getBitmap() != null) {
mImageIcon.setImageBitmap(response.getBitmap());
Bitmap icon = mIconGenerator.makeIcon();
Bitmap bhalfsize = Bitmap.createScaledBitmap(icon, 150,
150, false);
//Set has image flag
item.setHasImage(true);
//Find the right marker
MarkerManager.Collection markerCollection = mClusterManager.getMarkerCollection();
Collection<Marker> markers = markerCollection.getMarkers();
for (Marker m : markers) {
if (id.equals(m.getTitle())) {
//set the icon
m.setIcon(BitmapDescriptorFactory.fromBitmap(image));
break;
}
}
}
}
});
}
And your MyItem class must have some parameters which are useful for remember our stuff:
public class MyItem implements ClusterItem {
private String itemId;
private LatLng mPosition;
private WMWall wall;
private boolean hasImage = false;
public MyItem(double latitude, double longitude) {
mPosition = new LatLng(latitude, longitude);
}
#Override
public LatLng getPosition() {
return mPosition;
}
public WMWall getWall() {
return wall;
}
public void setWall(WMWall wall) {
this.wall = wall;
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public boolean hasImage() {
return hasImage;
}
public void setHasImage(boolean hasImage) {
this.hasImage = hasImage;
}
}
It is really important to load only the images of markers contained into bounds, otherwise you'll run into OOM.
And if the hasImage() method returns true we don't need to load the image again since it is already stored into the marker object.
I have a GoogleMap which displays fine (within a SupportMapFragment) and uses the GoogleMapOptions for the target camera location. However, I am unable to add markers/polylines to the GoogleMap. Here is the method for creating the map:
private void createMap(List<LatLng> latLngs) {
if(map == null) {
GoogleMapOptions options = new GoogleMapOptions();
mapFragment = SupportMapFragment.newInstance(options);
map = mapFragment.getMap();
float zoom = 13;
CameraPosition cameraP = new CameraPosition(latLngs.get(0), zoom, 0, 0);
options.camera(cameraP);
//TODO MAP IS NULL - SORT OUT!
// check it has been instantiated
if (map != null) {
Log.d(TAG, "map is not null");
map.clear();
//Calculate target zoom, based on trip size
map.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraP));
// Add LatLngs to polyline
PolylineOptions poly = new PolylineOptions().color(Color.RED);
MarkerOptions startMarker = new MarkerOptions()
.position(latLngs.get(0)).title("Start");
MarkerOptions endMarker = null;
if(latLngs.size() > 1) {
endMarker = new MarkerOptions().position(
latLngs.get(latLngs.size() - 1)).title("End");
}
for (LatLng latLng : latLngs) {
poly.add(latLng);
}
map.addPolyline(poly);
map.addMarker(startMarker);
map.addMarker(endMarker);
}
ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.trip_summary_map_container, mapFragment);
ft.commit();
}
}
As you can see from the inline comments, the map is still null (although it is displaying and using the options). Just can't add things to it. I assume I am not instantiating it properly?
The Activity extends FragmentActivity, and I have set up all the necessary stuff for using the Maps API.
Thank you for any help.
EDIT: I have posted a new answer with the solution I prefer to use now.
I had the same problem some days ago and I solved it extending SupportMapFragment class so that it executes a callback method once the map is finally ready.
public class ExtendedSupportMapFragment extends SupportMapFragment {
public static interface MapReadyListener {
public void mapIsReady(GoogleMap map);
}
#Deprecated
public static SupportMapFragment newInstance() {
return null;
}
#Deprecated
public static SupportMapFragment newInstance(GoogleMapOptions options) {
return null;
}
public static ExtendedSupportMapFragment newInstance(MapReadyListener mapReadyListener) {
ExtendedSupportMapFragment fragment = new ExtendedSupportMapFragment();
fragment.mapReadyListener = mapReadyListener;
return fragment;
}
private MapReadyListener mapReadyListener;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mapReadyListener != null)
mapReadyListener.mapIsReady(getMap());
}
}
Then you just need to do something like this:
public class RutaMapaFragment extends SherlockFragment implements ExtendedSupportMapFragment.MapReadyListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentMapa = ExtendedSupportMapFragment.newInstance(RutaMapaFragment.this);
}
...
#Override
public void mapIsReady(GoogleMap map) {
//Do whatever you want with your map.
}
}
So, time has passed and all. The fact is that I don't use the solution in my previous answer anymore, but prefer to use a ViewTreeObserver instead. The following code shows a rather simple fragment with a SupportMapFragment being added to it.
The method createMap() adds the SupportMapFragment and then executes setupMap(), but only via a OnGlobalLayoutListener that will basically be executed once the map is actually ready. Of course, this listener is removed immediately—there's no need to keep it there any longer.
public class MyMapFragment extends Fragment {
private View mMapContainer;
private SupportMapFragment mMapFragment;
private GoogleMap mMap;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(/* ... */);
// ...
mMapContainer = view.findViewById(R.id.map_fragment_container);
createMap();
return view;
}
private void createMap() {
mMapFragment = new SupportMapFragment();
getFragmentManager().beginTransaction()
.replace(R.id.map_fragment_container, mMapFragment)
.commit();
mMapContainer.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
mMapContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mMapContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
mMap = mMapFragment.getMap();
setupMap();
}
});
}
private void setupMap() {
mMap.setMyLocationEnabled(true);
// ...
}
}
Try to add the Marker & PolyLines in the map using below code:
GoogleMap mMap;
static final CameraPosition BONDI =
new CameraPosition.Builder().target(new LatLng(-33.891614, 151.276417))
.zoom(15.5f)
.bearing(300)
.tilt(50)
.build();
changeCamera(CameraUpdateFactory.newCameraPosition(BONDI));
mMap.addMarker(new MarkerOptions().position(new LatLng(-33.891614, 151.276417)).title("Bondi"));
private void changeCamera(CameraUpdate update) {
changeCamera(update, null);
}
/**
* Change the camera position by moving or animating the camera depending on the state of the
* animate toggle button.
*/
private void changeCamera(CameraUpdate update, CancelableCallback callback) {
boolean animated = ((CompoundButton) findViewById(R.id.animate)).isChecked();
if (animated) {
mMap.animateCamera(update, callback);
} else {
mMap.moveCamera(update);
}
}
Add the PolyLines as below:
// A simple polyline with the default options from Melbourne-Adelaide-Perth.
mMap.addPolyline((new PolylineOptions())
.add(BONDI));
am using the android support v4- google maps by pete doyle and am trying to delete a mapview and then recreate it again, when i go away from and return to it, but i keep getting the usual Java.lang.IllegalStateException: You are only allowed to have a single MapView in a MapActivity. anyone know ho to solve this correctly. I have this code in my fragment.
Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Log.d(TAG, "onCreate View called here in location fragment!!");
if(view == null){
view = inflater.inflate(R.layout.map, container, false);
frame = (FrameLayout)view.findViewById(R.id.mapContainer);
//mapview = (MapView)view.findViewById(R.id.mapView);
}
if(mapview == null){
mapview = new MapView(getActivity(), getActivity().getString(R.string.map_api_key)); // keep getting the error on this line
mapview_is_active = true;
}
mapview.setClickable(true);
mapview.setBuiltInZoomControls(true);
mapview.setSatellite(true); // Satellite View
mapview.setStreetView(true); // Street View
mapview.setTraffic(true); // Traffic View
frame.addView(mapview,0);
//frame = (FrameLayout)view.findViewById(R.id.mapContainer);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
if(mapview == null){
Log.d(TAG, "mapview is null here");
mapview = new MapView(getActivity(), getActivity().getString(R.string.map_api_key));
}
if(frame.getChildAt(0) == null){
Log.d(TAG, "mapview is null here in container");
frame.addView(mapview);
}
MapController controller = mapview.getController();
double lat = Double.parseDouble(longitude);
double lon = Double.parseDouble(latitude);
GeoPoint geopoint = new GeoPoint((int)(lat * 1E6), (int)(lon * 1E6));
mapview.invalidate();
List<Overlay> mapOverlays = mapview.getOverlays();
Drawable drawable = this.getResources().getDrawable(R.drawable.map_point);
AddMapOverlay add_map_overlay = new AddMapOverlay(drawable, getActivity());
OverlayItem overlayitem = new OverlayItem(geopoint, name, address);
add_map_overlay.addOverlay(overlayitem);
mapOverlays.add(add_map_overlay);
controller.animateTo(geopoint);
controller.setZoom(15);
}
and in my onStop():
#Override
public void onStop(){
super.onStop();
if(frame != null){
frame.removeView(mapview); // i remove the mapview here
}
}
here is my map.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapContainer"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
</FrameLayout>
any help will be greatly appreciated. Thanks
I did this with the below code. I have a MapView instance variable in my FragmentActivity, and then added and removed it in the Fragment itself below (just included the pertinent parts):
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mLayoutMap = (FrameLayout) getActivity().findViewById(R.id.layoutMap);
// create the map view if it's not already there
if (((MyActivity)getActivity()).mMapView == null)
{
((MyActivity)getActivity()).mMapView =
new MapView(getActivity(), getActivity().getString(R.string.map_api_key));
((MyActivity)getActivity()).mMapView.setClickable(true);
}
// add the map view to the frame layout, at the lowest z-index
mLayoutMap.addView(((MyActivity)getActivity()).mMapView, 0);
}
#Override
public void onStop()
{
super.onStop();
if (mLayoutMap.getChildCount() > 0 && mLayoutMap.getChildAt(0) instanceof MapView)
mLayoutMap.removeViewAt(0);
}
mLayoutMap is just a FrameLayout in my XML.
Usually I did this (use android-support-v4-google_maps as you):
1) Implement to activity interface like IMapActivity with methods of attachMap and detachMap
public class MyMapActivity extends FragmentActivity implements IMapActivity {
private MapView mMapView;
public void attachMap(ViewGroup mapHolder) {
if (mMapView == null) {
mMapView = getLayoutInflater().inflate(R.id.mapview, null);
}
mapHolder.addView(mMapView);
}
public void detachMap(ViewGroup mapHolder) {
mapHolder.removeView(mMapView);
}
}
2) Call that methods when need to attach MapView or detach MapView on Fragment.onDestroyView()
public class MyMapFragment extends Fragment {
private IMapActivity mMapViewHolder;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mMapViewHolder = (IMapActivity) activity;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mMapViewHolder.attachMap(getMapHolder());
}
#Override
public void onDestroyView() {
mMapViewHolder.detachMap(getMapHolder());
super.onDestroyView();
}
}
I keep in a separate xml MapView with the some settings. When it is necessary to destroy the Fragment - detach MapView, attach MapView when the Fragment is recreated (you can also update the controller of MapView after the attach). This allows me to reuse fragments MapView.
P.S.
I just have not found any way how to clean MapActivity of links to the MapViev. If there is a way, I would be very grateful.
Try using frame.removeAllViews()
If you are working with navHost, you need to add it programmatically not in XML because it will be duplicated.
val mapFragment = SupportMapFragment.newInstance()
requireActivity().supportFragmentManager
.beginTransaction()
.add(R.id.mapFragment, mapFragment)
.commit()
First of all, i don't know where, i don't know why, i get ConcurrentModificationException. My Logcat is out of order (or just i can't use it) but never shows any information about exceptions ( i read lots of article about it, and nothing helped, maybe can't debug my phone correctly )
Secondly sorry about my confused english, i try to formulate as clearly as i can, codes could help, please help me
So, The problem is the next:
i use Mapview and 5 custom CustomItemizedOverlay (Source no. 1) on it.
From MapView i start some (1, 2, max 5) threads to webservice (Source no. 2) and after i get results back (it's List) i draw them into mapoverlay (source no. 3)
so MapView ( implements 5 ResponseHandlerInterfaces ) sends requests to webservice through myActions (extends Thread) and when actions gets responses, they call responseHandler.reportResponseList(List list) methods. (MapView get back the control right here)
and all of it causes ConcurrentModificationException sometimes
(rarely ArrayIndexOutOfBoundsException)
i have got Options Activity to set required lists and i also have got Refresh button, to get lists. let me lead you through one example.
I just opened MapView, it's empty. I need only 1 kind of objects. I tap refresh, after network communication, i get markers on my mapview. cool, it's working. Now i'm going to Options, and i set more objects to request. Use Refresh again at mapview, and sometimes i get all kinds of objects, sometimes i get ConcurrentModificationException.
Source No. 1
public class CustomItemizedOverlay<T> extends ItemizedOverlay<T>{
private Context mContext;
private Object lock = new Object();
private CopyOnWriteArrayList<T> overlays = new CopyOnWriteArrayList<T>();
public CustomItemizedOverlay(Drawable marker, Context context) {
super(boundCenterBottom(marker));
this.mContext = context;
populate();
}
#Override
protected boolean onTap(int index){
// doesn't matter
}
public void clear(){
synchronized (lock) {
overlays.clear();
}
}
public void addOverlay(T overlay){
synchronized (lock) {
overlays.add(overlay);
setLastFocusedIndex(-1);
populate();
}
}
public void removeOverlay(int selected){
synchronized (lock) {
overlays.remove(selected);
populate();
setLastFocusedIndex(-1);
}
}
#Override
protected T createItem(int i) {
synchronized (lock) {
return overlays.get(i);
}
}
#Override
public int size() {
synchronized (lock) {
return overlays.size();
}
}
public void setLock(Object o){
this.lock = o;
}
}
Source No. 2
MapView:
public class MyMap extends MapActivity implements LocationListener, RestResPonseHandler { // there are 5 type of responsehandlers, one for each overlay
private MapView mapView;
private MyLocationOverlay myLocationOverlay;
private Object lock = new Object();
private CustomItemizedOverlay<CustomOverlayItem<MyObject1>> my1Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject2>> my2Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject3>> my3Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject4>> my4Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject5>> my5Overlay;
public void getObject1List(){ // there are 5 getList methods
new RestAction(this).start(); // 'this' is the object which implements required RestResponseHandler interface. in every case it will be 'this'. MyMap implements all kind of required RestResponseHandler interfaces
}
Source No. 3 (non main thread) // This is pattern for each 'CustomItemizedOverlay filling method'. After actions reports results (list of objects), mapview fills actual overlay with OverlayItems
#Override
public void reportResponseList(List<MyObject1> objects) {
if (my1Overlay == null){
List<Overlay> mapOverlays = mapView.getOverlays();
Drawable marker = this.getResources().getDrawable(R.drawable.icon);
my1Overlay = new CustomItemizedOverlay<CustomOverlayItem<MyObject1>>(marker, this);
my1Overlay.setLock(lock); // MyMap has lock object, look at source 2 (also CustomItemizedOverlay (source 1) )
mapOverlays.add(my1Overlay);
} else {
my1Overlay.clear();
}
synchronized (lock) {
for(int i=0;i<objects.size();++i){
MyObject1 object = objects.get(i);
CustomOverlayItem<MyObject1> item = new CustomOverlayItem<CustomBuilding>(object.getPositionId(), object);
my1Overlay.addOverlay(item);
}
refreshView();
}
}
Where refreshView posts runnable to main thread to update mapView.
public void refreshView(){
new Thread(new Runnable(){
#Override
public void run(){
mapView.post(new Runnable(){
#Override
public void run(){
mapView.invalidate();
}
});
}
}).start();
}
The Solution:
After CommonsWare's answer, i modified my source to :
#Override
public void reportResponseList(final List<MyObject1> objects) {
if (my1Overlay == null){
List<Overlay> mapOverlays = mapView.getOverlays();
Drawable marker = this.getResources().getDrawable(R.drawable.icon);
my1Overlay = new CustomItemizedOverlay<CustomOverlayItem<MyObject1>>(marker, this);
my1Overlay.setLock(lock);
mapOverlays.add(my1Overlay);
} else {
runOnUiThread(new Runnable(){
#Override
public void run(){
my1Overlay.clear();
}
});
}
runOnUiThread(new Runnable(){
#Override
public void run(){
for(int i=0;i<objects.size();++i){
MyObject1 object = objects.get(i);
CustomOverlayItem<MyObject1> item = new CustomOverlayItem<MyObject1>(object.getPositionId(), object);
my1Overlay.addOverlay(item);
}
refreshView();
}
});
}
and now at this moment it seems to work. i don't know how pretty is it, but seems to work. (maybe mapOverlays.add() method should be on main thread too) Thank you very much.
If my1Overlay is already part of the MapView by the time reportResponseList() is called, you should not be modifying it on a background thread. MapView will be using that Overlay. Instead, create a new Overlay in the background thread, the swap overlays (remove the old, add the new) on the main application thread.