I am working on Android Google Map and I am using MapApiV2. I am able to show the map, user location, markers on the map. My problem is that I want to open an InfoWindow when user click on any marker on the map. I am able to open the window also but I need to show more data in the info video so I am trying to pass the my CustomClass object in the .snippet() methos but it except only string object. So I convert my class object to string by simply doing this (uv.tostring) and pass to snippet() method. Now on the other side when I trying to get the dat from snippet using marker.getSnippt(); I can not access my data from the string. So please suggest me how can I pass my custom class object to snippt so I can acces my get, set methods of custom class.
Below is my code
My Custom class is:
public class UserVerifyer implements UserVerfierInterface{
// <userPojo>
// <frequency>0.0</frequency>
// <message/>
// <status>NOTOK</status>
// <token>ey0dok0vozrhwhe98rt6ydbs</token>
// <url/>
// </userPojo>
private String frequency = "", message = "", status = "", token = "",
url = "";
public static String statusOk = "OK";
public void setFrequency(String freq) {
this.frequency = freq;
}
public String getFrequency() {
return frequency.trim();
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message.trim();
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return status.trim();
}
public void setToken(String token) {
this.token = token;
}
public String getToken() {
return token.trim();
}
public void setUrl(String url) {
this.url = url;
}
public String getUrl() {
return url.trim();
}
}
MyCustomInfo window class is:
public class CustomMarkers implements InfoWindowAdapter {
// private Context context;
// private String text;
// private Integer image;
private final View mWindow;
private final View mContents;
public CustomMarkers(Activity activity) {
// this.context = ctx;
// this.text = text;
// this.image = image;
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mWindow = inflater.inflate(R.layout.balloon, null);
mContents = inflater.inflate(R.layout.balloon, null);
}
#Override
public View getInfoContents(Marker marker) {
// TODO Auto-generated method stub
render(marker, mWindow);
return mWindow;
}
#Override
public View getInfoWindow(Marker marker) {
// TODO Auto-generated method stub
render(marker, mContents);
return mContents;
}
private void render(Marker marker, View view) {
String title = marker.getTitle();
TextView titleUi = ((TextView) view.findViewById(R.id.txtTitle));
if (title != null) {
titleUi.setText(title);
} else {
titleUi.setText("");
}
String snippet = marker.getSnippet();
UserVerifyer uv = UserVerifyer.class.cast(snippet);
System.out.println("Sttaus in Marker= "+uv.getStatus());
// uv.getStatus();
TextView snippetUi = ((TextView) view.findViewById(R.id.txtPlace));
if (snippet != null) {
snippetUi.setText(snippet);
} else {
snippetUi.setText("");
}
}
}
and I am adding data in marker like this:
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(new LatLng(getMyLocation().getLatitude(),
getMyLocation().getLongitude())).zoom(17).bearing(90)
.tilt(30).build();
mMap.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraPosition));
UserVerifyer uv = new UserVerifyer();
mMap.addMarker(new MarkerOptions()
.position(
new LatLng(getMyLocation().getLatitude(),
getMyLocation().getLongitude()))
.title("Me")
.snippet(uv.toString())
.icon(BitmapDescriptorFactory.fromResource(R.drawable.user_loc)));
mMap.setInfoWindowAdapter(new CustomMarkers(RecomendationScreen.this));
Please suggest me how can I do this.
Thanks.
Solution:
Instead of converting to string and dive into complexity,I would like to suggest you alternative approach to achieve the desired result in very efficient manner.
You can create a HashMap<Marker, UserVerifyer> ,and you can retrieve object of UserVerifyer by using Marker as the key.so you can easily put the data and retrieve the data from HashMap.
Reference:
Have a look at this example.It is performing exactly what I mean to say.
I hope it will be helpful !!
MehulJoisar's suggestion of using Map<Marker, YourModel> is the usual way to work around the limitation of Android API v2, but there is also another way.
You may use Android Maps Extensions for your task. It adds setData and getData functions to Marker class, so your code would look like this:
UserVerifyer uv = new UserVerifyer();
Marker marker = mMap.addMarker(new MarkerOptions()
.position(
new LatLng(getMyLocation().getLatitude(),
getMyLocation().getLongitude()))
.title("Me")
.icon(BitmapDescriptorFactory.fromResource(R.drawable.user_loc)));
marker.setData(uv);
and later in GoogleMap callback:
//String snippet = marker.getSnippet();
UserVerifyer uv = (UserVerifyer) marker.getData();
System.out.println("Sttaus in Marker= "+uv.getStatus());
Related
I want to load images asynchronously and show them on the infowindow. For this I made a custom class to store parameters. I need for this (marker, image) but my code throws a runtime exception saying it can't call getPosition on the marker I stored in the custom class instance.
What is the correct way to use AsyncTask with custom class instance parameters?
This is my code:
googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
LatLng latLng = marker.getPosition();
// find location id in database
Location location = dbhandler.getLocationByLatLng(latLng);
final int id = location.getId();
addButton.setVisibility(View.VISIBLE);
addButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// open load image fragment
android.support.v4.app.FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
android.support.v4.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
LoadImageFragment fragment = new LoadImageFragment();
// pass id to new fragment
Bundle bundle = new Bundle();
bundle.putInt("id", id);
fragment.setArguments(bundle);
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
});
removeButton.setVisibility(View.VISIBLE);
removeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// remove markers and images
}
});
class TaskParams {
Marker marker;
Location location;
Image image;
TaskParams() {
}
public Location getLocation() {
return this.location;
}
public Marker getMarker() {
return this.marker;
}
public Image getImage(){
return this.image;
}
public void setMarker(Marker marker) {
this.marker = marker;
}
public void setLocation(Location location) {
this.location = location;
}
public void setImage(Image image) {
this.image = image;
}
}
TaskParams taskParams = new TaskParams();
taskParams.setMarker(marker);
new AsyncTask<TaskParams, Void, TaskParams>() {
#Override
protected TaskParams doInBackground(TaskParams... params) {
TaskParams tParams = params[0];
Marker m = tParams.getMarker();
LatLng latLng = m.getPosition();
Location location = dbhandler.getLocationByLatLng(latLng);
tParams.setLocation(location);
return tParams;
}
// find image and text associated with Location
protected void onPostExecute(TaskParams taskParams) {
new AsyncTask<TaskParams, Void, TaskParams>() {
#Override
protected TaskParams doInBackground(TaskParams... params) {
TaskParams tParams = params[0];
Location location = tParams.getLocation();
try {
image = dbhandler.getImageByLocationId(location.getId());
tParams.setImage(image);
}
catch (Exception ex){
Log.d("debug", "failed to fetch image");
image = null;
}
return tParams;
}
#Override
protected void onPostExecute(TaskParams taskParams) {
Image image = taskParams.getImage();
// set image and description
if(image != null) {
infoImageView.setImageBitmap(image.getBitmap());
infoTextView.setText(image.getDescription());
Marker marker = taskParams.getMarker();
marker.showInfoWindow();
updateInfoWindow(image);
}
}
}.execute(taskParams);
}
}.execute(taskParams);
//marker.showInfoWindow();
return true;
}
});
// find Location in database
// Setting a custom info window adapter for the google map
googleMap.setInfoWindowAdapter(new InfoWindowAdapter() {
// Use default InfoWindow frame
#Override
public View getInfoWindow(Marker arg0) {
return null;
}
// Defines the contents of the InfoWindow
#Override
public View getInfoContents(Marker arg0) {
// Getting view from the layout file info_window_layout
View v = getActivity().getLayoutInflater().inflate(R.layout.info_window_layout, null);
// Getting the position from the marker
final LatLng latLng = arg0.getPosition();
infoImageView = (ImageView) v.findViewById(R.id.infoImage);
infoTextView = (TextView) v.findViewById(R.id.infoText);
if(image != null) {
infoImageView.setImageBitmap(image.getBitmap());
infoTextView.setText(image.getDescription());
}
return v;
}
});
You can override the constructor. Something like:
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
public MyAsyncTask(boolean showLoading) {
super();
// do stuff
}
// doInBackground() et al.
}
Then, when calling the task, do something like:
new MyAsyncTask(true).execute(maybe_other_params);
This is more useful than creating member variables because it simplifies the task invocation. Compare the code above with:
MyAsyncTask task = new MyAsyncTask();
task.showLoading = false;
task.execute();
Without using the ClusterManager, I use HashMap to put the Marker and ID into the HashMap, and get the ID in the OnMarkClick method and get the data from database. It's works
markers.put(addNewMarker(geoPoint), objectId);
private Marker addNewMarker(ParseGeoPoint parseGeoPoint) {
double latitude = parseGeoPoint.getLatitude();
double longitude = parseGeoPoint.getLongitude();
return googleMap.addMarker(new MarkerOptions().position(
new LatLng(latitude, longitude)));
}
#Override
public boolean onMarkerClick(Marker marker) {
String objectId = markers.get(marker);
if (null == objectId) {
return false;
}
getMemoryBriefInfo(objectId);
return true;
}
But now I need to use the ClusterManager to cluster multiple markers into number.
The problems is It seems there is no way to implement this, in the demo of Google, it just add the Items into the Cluster.
There is a OnMarkerClick method in the ClusterManager class, but I don't how to Override this and set with my own unique ID.
there is a global solution for you that help to add title, snippet and icon so you can get what you want.
Modify your ClusterItem Object and add 3 variables :
public class MyItem implements ClusterItem {
private final LatLng mPosition;
BitmapDescriptor icon;
String title;
String snippet;
public MyItem(BitmapDescriptor ic,Double lat , Double lng,String tit ,String sni)
{
mPosition = new LatLng(lat,lng);
icon = ic;
title = tit;
snippet = sni;
}
And after you create your costume render :
public class OwnRendring extends DefaultClusterRenderer<MyItem> {
public OwnRendring(Context context, GoogleMap map,
ClusterManager<MyItem> clusterManager) {
super(context, map, clusterManager);
}
protected void onBeforeClusterItemRendered(MyItem item, MarkerOptions markerOptions) {
markerOptions.icon(item.getIcon());
markerOptions.snippet(item.getSnippet());
markerOptions.title(item.getTitle());
super.onBeforeClusterItemRendered(item, markerOptions);
}
}
After that just put this line in your SetUpCluster() function before addItems():
mClusterManager.setRenderer(new OwnRendring(getApplicationContext(),mMap,mClusterManager));
I'm currently developing an Android app that have to receive GPS positions from other devices every minutes, and display them on a map.
I'm using the GoogleMaps Api V2,
What i'd like to do, is to refresh the position marker very time a new position is received. (I don't want to refresh the whole map)
For the moment, I've added a button in the menu that enables me to refresh the entire map.
To detail to structure, I have a service that run MQTT, and every time a position is received, I add it into an Hashmap, that represents my map data source.
This HashMap is a Singleton that extends Observable.
Moreover, my fragment that display the my implements Observer.
Code from my Fragment that implements Observer
public void update(Observable observable, final Object object)
{
if (observable instanceof ListPositions && object != null)
{
Position p = (Position) object;
LatLng position = new LatLng(p.getLatitude(), p.getLongitude());
// Where i'd like to move the markers
}
else
{
// Where i'd like to remove the marker from the map
}
}
Code From my Singleton List of position
public class ListPositions extends Observable{
private HashMap<String,Position> mapPosition;
private ListPositions()
{
mapPosition = new HashMap<String, Position>();
VehicleMapFragment mapFragmentObserver = new VehicleMapFragment();
this.addObserver(mapFragmentObserver);
}
private static ListPositions INSTANCE = null;
public static synchronized ListPositions getInstance()
{
if (INSTANCE == null)
{ INSTANCE = new ListPositions();
}
return INSTANCE;
}
public int getNumberOfPosition()
{
return mapPosition.size();
}
public void addPosition(String key, Position p){
mapPosition.put(key,p);
setChanged();
notifyObservers(p);
}
public void removePosition(String key){
mapPosition.remove(key);
setChanged();
notifyObservers();
}
Code From myService that runs MQTT
public void onPositionMessageReceived(MqttMessage message, String source)
{
Gson gson = new Gson();
String s = gson.toJson(message.toString());
String jsonPosition = gson.toJson(message.toString());
jsonPosition = formatMessage(jsonPosition);
Position p = gson.fromJson(jsonPosition, Position.class);
ListPositions.getInstance().addPosition(source, p);
}
Can someone know how to move each markers individually without refreshing the whole map, in my update function from my Observer Fragment?
May I use a Handler to update the Map, from an other thread to modify the Main UI Thread ?
Many thanks
EDIT :
Because the first methode given by AniV didn't work for me, I've tried with an Asyntask that runs when my Observer get a notification from the Observable List.
Code from the Observer Fragment :
public void update(Observable observable, final Object object)
{
if (observable instanceof ListPositions && object != null)
{
Position p = (Position) object;
position = new LatLng(p.getLatitude(), p.getLongitude());
options = new MarkerOptions().position(position).title("TEST").snippet("TEST");
PositionUpdate positionUpdaterTask = new PositionUpdate(myGoogleMap, options, position);
positionUpdaterTask.execute();
}
}
Code from the AsyncTask :
public class PositionUpdate extends AsyncTask<Void, Void, Void>{
private GoogleMap myGoogleMap;
private MarkerOptions options;
private LatLng positionToAdd;
public PositionUpdate(GoogleMap googleMap, MarkerOptions options, LatLng position)
{
this.myGoogleMap = googleMap;
this.options = options;
this.positionToAdd = position;
}
#Override
protected Void doInBackground(Void...voids)
{
return null;
}
#Override
protected void onPostExecute(Void aVoid)
{
if (myGoogleMap != null)
{
myGoogleMap.addMarker(options.position(positionToAdd));
Log.i(ConstElisa.LOG_ELISA, "MARKER ADDED");
}
else
{
Log.e(ConstElisa.LOG_ELISA, "ERROR ADDING THE MARKER");
}
super.onPostExecute(aVoid);
}
However, in this case, myGoogleMap variable is always null, so the marker is never added to the Google Map.
Does someone have an idea why this variable is null ?
I finally succeeded in doing that thing by using the AsyncTask.
In my EDIT, I said that I had some trouble with the null instance of my Google Maps object.
This was caused by my Singleton Observable. Indeed, in the constructor, I used
VehicleMapFragment mapFragmentObserver = new VehicleMapFragment();
this.addObserver(mapFragmentObserver);
This code sample recreated another instance of my Fragment, and that the reason why ii had Null objects.
To correct this problem, I simply used :
ListPositions.getInstance().addObserver(this);
in my Fragment Observer.
So if you want to update a marker position without refreshing the whole map,
you can use the Observer/Observable Pattern, and use an Asynctask to update the marker position.
You have two options here:
Either update the marker position programatically using setPosition() method.
Marker marker = googleMap.addMarker(new MarkerOptions().position(entry.getValue()).title(entry.getKey()));
Use this object to change its position:
marker.setPosition(new LatLng(5, 5));
OR as you said, Make use of Handlers:
Handler locationHandler;
final static long REFRESH = 10 * 1000;
final static int SUBJECT = 0;
private GoogleMap mMap;
private Marker myMarker = null;
and onCreate()
locationHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == SUBJECT) {
updateMarker();
this.sendEmptyMessageDelayed(SUBJECT, REFRESH);
}
}
};
Handler option is better in cases where you have to update the position at a specific time interval.
I have something like this. I receive a list of devices every 5 minutes to update locations of the map.
What do you think about this
private HashMap<String, Marker> mMarkers = new HashMap<>();
private void drawDevicesOnMap() {
if (isMapReady) {
for (Device device : mDeviceList) {
List<com.model.Location> locationList = device.getLocations();
if (locationList != null && !locationList.isEmpty()) {
if (mMarkers.containsKey(device.getId())) { //update
Marker m = mMarkers.get(device.getId());
m.setPosition(device.getLocations().get(0).getCoordinates().getLatLng());
mMarkers.put(device.getId(), m);
} else { //add
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(locationList.get(0).getCoordinates().getLatLng());
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(device.getHue()));
markerOptions.title(device.getNickname());
Marker marker = mMap.addMarker(markerOptions);
mMarkers.put(device.getId(), marker);
}
}
}
}
}
if the marker for a device with x id is found in the HashMap, you update its location.
I am working on an application using a Google Map and the Google Places API, let's say I populate the map with different Markers and that I keep track of those Markers inside a
Map<Marker, Place> places = new HashMap<Marker, Place>();
Here is my Place's class :
public class Place {
String placeId;
String name;
public Place(String placeId, String name) {
this.placeId = placeId;
this.name = name;
}
}
I would like to be able to dynamically fill an InfoWindow with data fetched based on the placeId argument, here is what I do inside the InfoWindowAdapter :
map.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker marker) {
return null;
}
#Override
public View getInfoContents(Marker marker) {
View v = getLayoutInflater().inflate(R.layout.info_window_layout, null);
TextView placeName = (TextView)v.findViewById(R.id.info_window_place_name);
Place place = places.get(marker);
if (place != null) {
placeName.setText(place.name);
String photoUrl = "http://www.plopcontenido.com/wp/wp-content/themes/PlopTheme/img/logo.png";
new DownloadPlacePhoto(v, marker, placeName.toString()).execute(photoUrl);
}
return v;
}
});
private class DownloadPlacePhoto extends AsyncTask<String, Void, Bitmap> {
View v;
Marker marker;
String placeName;
public DownloadPlacePhoto(View v, Marker marker, String placeName) {
this.v = v;
this.marker = marker;
this.placeName = placeName;
}
#Override
protected Bitmap doInBackground(String... urls) {
Bitmap download;
try {
InputStream in = new URL(urls[0]).openStream();
download = BitmapFactory.decodeStream(in);
return download;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Bitmap download) {
if (download != null) {
ImageView placeImage = (ImageView)v.findViewById(R.id.info_window_place_photo);
placeImage.setImageBitmap(download);
placeImage.setContentDescription(this.placeName);
placeImage.setVisibility(View.VISIBLE);
if (this.marker.isInfoWindowShown()) {
this.marker.hideInfoWindow();
this.marker.showInfoWindow();
}
}
}
}
The thing is the InfoWindow is a "snapshot" and not a live representation (and I totally understand why) is that the associated Asynctask will run inside another thread so the snapshot will already be taken without my fetched data.
I heard people talking about Observer Pattern and other talking about "you need to get your data stored before you enter the getInfoWindow function, but due to Google Places limitations I can't afford to perform two more requests (one for the picture, and the other one for more data about a specific place) for each Marker.
Any idea about how I could perform this ?
The gist of this question & answer is to re-show the InfoWindow with marker.showInfoWindow() when you have all the data you need and until then you could show the data you already have or some "loading ..."-text.
When doing so, you have to check if the user unselected the respective marker or selected another marker in the meantime.
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.