I am making an android application that needs to search in my local area within 10km and display the results onto a map using pins, For example: "Starbucks", "Wallmart", Shopping mall, etc. The search word that i specify in my activity class. And to be clear: I do NOT want to open the search in Google maps, i want it to display the results inside MY own application. But i get an error at the code that executes the search. The error comes up on the following things:
Url: url cannot be resolved or is not a field
Execute: The method execute() is undefined for the type HttpRequest
Response: response cannot be resolved or is not a field
I am using three packages:
com.mycompany.applicationname = Default package, containing main code, including the search code
com.mycompany.applicationname.Model = Containing PlaceAutoComplete, PlacesList, Place, etc.
com.mycompany.applicationname.PlacesRequests = Containing PlaceRequest.java
Please help me, i really need help and thanks SO much in advance
This is the code that i am using to execute the search:
private static final String PLACES_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/search/json?";
private static final boolean PRINT_AS_STRING = false;
public void performSearch() throws Exception {
try {
System.out.println("Perform Search ....");
System.out.println("-------------------");
HttpRequestFactory httpRequestFactory = createRequestFactory(transport);
HttpRequest request = httpRequestFactory.buildGetRequest(new GenericUrl(PLACES_SEARCH_URL));
request.url.put("key", API_KEY);
request.url.put("location", lat + "," + lng);
request.url.put("radius", 500);
request.url.put("sensor", "false");
if (PRINT_AS_STRING) {
System.out.println(request.execute().parseAsString());
} else {
PlacesList places = request.execute().parseAs(PlacesList.class);
System.out.println("STATUS = " + places.status);
for (Place place : places.results) {
System.out.println(place);
}
}
} catch (HttpResponseException e) {
System.err.println(e.response.parseAsString());
throw e;
}
}
As far as the HTTP interface goes, your code LGTM assuming API_KEY holds a valid API key. That is, one created in the API console. Try printing out the whole request.url and see if it looks like this:
https://maps.googleapis.com/maps/api/place/search/json?key=AIzaSyDoTeTuPXXXXXXXXXMHPVYM5VTg&location=37.994682,-87.6045923&radius=500&sensor=false
Also see this thread because e.response isn't valid, maybe just remove that call to println and see what e looks like when it's thrown.
You can do this by using Google API JAVA Client - Here is an example using the java client for getting all the 60 results.
public PlacesList search(double latitude, double longitude, double radius, String types)
throws Exception {
try {
HttpRequestFactory httpRequestFactory = createRequestFactory(HTTP_TRANSPORT);
HttpRequest request = httpRequestFactory
.buildGetRequest(new GenericUrl("https://maps.googleapis.com/maps/api/place/search/json?"));
request.getUrl().put("key", YOUR_API_KEY);
request.getUrl().put("location", latitude + "," + longitude);
request.getUrl().put("radius", radius);
request.getUrl().put("sensor", "false");
request.getUrl().put("types", types);
PlacesList list = request.execute().parseAs(PlacesList.class);
if(list.next_page_token!=null || list.next_page_token!=""){
Thread.sleep(4000);
/*Since the token can be used after a short time it has been generated*/
request.getUrl().put("pagetoken",list.next_page_token);
PlacesList temp = request.execute().parseAs(PlacesList.class);
list.results.addAll(temp.results);
if(temp.next_page_token!=null||temp.next_page_token!=""){
Thread.sleep(4000);
request.getUrl().put("pagetoken",temp.next_page_token);
PlacesList tempList = request.execute().parseAs(PlacesList.class);
list.results.addAll(tempList.results);
}
}
return list;
} catch (HttpResponseException e) {
return null;
}
}
Related
i'm using for firts time Retrofit on my Android app.
This is the structure of json object that i have to retrive:
{
"placemarks":[
{
"address":"Via di Santa Maria a Marignolle, 15, 50124 Firenze",
"coordinates":[
11.23348,
43.75855,
0
],
"engineType":"CE",
"exterior":"GOOD",
"fuel":100,
"interior":"GOOD",
"name":"049/EV284TP",
"smartPhoneRequired":false,
"vin":"WME4513341K774636"
}
]
}
i have write this Pojo model for object "placemarks" and all otehr items.
And i have write this code to retrive the json data and put it into map:
private void getPlacemark(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.car2go.com/api/v2.1/vehicles?loc=roma&oauth_consumer_key=roadzapp&format=json")
.addConverterFactory(GsonConverterFactory.create())
.build();
APIService service = retrofit.create(APIService.class);
Call<ResponsePlacemarks> call = service.getPlacemark();
call.enqueue(new Callback<ResponsePlacemarks>() {
#Override
public void onResponse(Response<ResponsePlacemarks> response, Retrofit retrofit) {
Log.d("response: ", String.valueOf(response.body()));
try {
mMap.clear();
// This loop will go through all the results and add marker on each location.
for (int i = 0; i < response.body().getPlacemarks().size(); i++) {
Double lat = response.body().getPlacemarks().get(i).getCoordinates().get(1);
Double lng = response.body().getPlacemarks().get(i).getCoordinates().get(0);
String placeName = response.body().getPlacemarks().get(i).getAddress();
MarkerOptions markerOptions = new MarkerOptions();
LatLng latLng = new LatLng(lat, lng);
// Position of Marker on Map
markerOptions.position(latLng);
// Adding Title to the Marker
markerOptions.title(placeName);
// Adding Marker to the Camera.
Marker m = mMap.addMarker(markerOptions);
// Adding colour to the marker
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
// move map camera
mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
mMap.animateCamera(CameraUpdateFactory.zoomTo(11));
}
} catch (Exception e) {
Log.d("onResponse", "There is an error");
e.printStackTrace();
}
}
#Override
public void onFailure(Throwable t) {
}
});
}
but now when i run app the json are note load and i have error in the first line of for loop:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List android.mobility.com.mobiity.model.ResponsePlacemarks.getPlacemarks()' on a null object reference
Hi have found the error.
On APIService i have set #GET method in this way: #GET(".") because i use as URL the URL into BaseURL. So if i set all url into #GET the method works fine.
How i can use only the url into BaseULRalso into #GET?
First, you can only call response.body() exactly once.
So, comment this.
// Log.d("response: ", String.valueOf(response.body()));
And extract that list variable
final ResponsePlacemarks _response = response.body();
final List<Placemark> placemarks = _response.getPlacemarks();
for (int i = 0; i < placemarks.size(); i++) {
final Placemark p = placemarks.get(i);
Coordinates c = p.getCoordinates();
Double lat = c.get(1);
Double lng = c.get(0);
String placeName = p.getAddress();
And if that doesn't work, then you need for the Java object to exactly match the JSON response, otherwise it is null
How i can use only the url into BaseULRalso into #GET?
Your base URL should look like something this
https://www.car2go.com/api/v2.1
Then you should be able to have something like
#GET("/vehicles")
public ResponsePlacemarks getVehicles(
#Query("oauth_consumer_key") String key,
#Query("format") String format
#Query("loc") String loc
);
public ResponsePlacemarks getVehicles(String loc) {
return getVehicles("roadzapp", "json", loc);
}
Or maybe just
#GET("/vehicles?format=json")
public ResponsePlacemarks getVehicles(
#Query("oauth_consumer_key") String key,
#Query("loc") String loc
);
The reason for putting the key into the method call is that you shouldn't store the key as a string on your device for security reasons.
Most likely your POJO does not match the JSON structure.
Can you post your POJO class and how are you deserializing the JSON?
So in my app I am making use of google map apis and I'm using Geocoding to determine the Address based on the user's current location. I was using the Geocoder Android Class but I've found that it truly works terribly. It's just not reliable. So I used a post I saw here at SO to create my own Geocoder. Problem is, I now don't know if I'm using server side or client side geocoding. This is kind of important because one has a limit and the other really doesn't. All of my code is in Android though.
Here's some code, this is within my "MyGeocoder" Class:
public List<Address> getFromLocation(double latitude, double longitude,
int maxResults) throws IOException, LimitExceededException {
if (latitude < -90.0 || latitude > 90.0) {
throw new IllegalArgumentException("latitude == " + latitude);
}
if (longitude < -180.0 || longitude > 180.0) {
throw new IllegalArgumentException("longitude == " + longitude);
}
if (isLimitExceeded(context)) {
throw new LimitExceededException();
}
final List<Address> results = new ArrayList<Address>();
final StringBuilder url = new StringBuilder(
"http://maps.googleapis.com/maps/api/geocode/json?sensor=true&latlng=");
url.append(latitude);
url.append(',');
url.append(longitude);
url.append("&language=");
url.append(Locale.getDefault().getLanguage());
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url.toString());
try {
HttpResponse response = httpclient.execute(httppost);
String jsonResult = inputStreamToString(
response.getEntity().getContent()).toString();
Gson gson = new Gson();
MyGeocodeResponse geocodeResponse = gson.fromJson(jsonResult, MyGeocodeResponse.class);
final Address current = new Address(Locale.getDefault());
if(geocodeResponse.getStatus().equals(STATUS_OK)) {
MyGeocode locGeocode= geocodeResponse.getResults().get(0);
String streetAddress = "";
for(MyAddressComponent component : locGeocode.getAddress_components()) {
for(String type : component.getTypes()) {
if(type.equals("locality")) {
current.setLocality(component.getLong_name());
}
if(type.equals("administrative_area_level_1")) {
current.setAdminArea(component.getLong_name());
}
if(type.equals("street_number")) {
if(streetAddress.length() != 0) {
current.setAddressLine(0, component.getLong_name() + " " + streetAddress);
} else {
streetAddress = component.getLong_name();
}
}
if(type.equals("route")) {
if(streetAddress.length() != 0) {
current.setAddressLine(0, streetAddress + " " + component.getShort_name());
} else {
streetAddress = component.getShort_name();
}
}
}
}
current.setLatitude(latitude);
current.setLongitude(longitude);
results.add(current);
}
Log.i("TEST", "Got it");
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return results;
}
Edit:
And I guess a further questions is, if this is server-side Geocoding, then can this code only be run 2,500 times per day period, or can it be run 2,500 times daily per user of the app? If it's the first option I'm still ok, but if it's the 2nd option I don't see how any app that wants to have a half-way big user base can use server-side geocoding without hitting that limit.
I now don't know if I'm using server side or client side geocoding
after looking to your code you wrote http://maps.googleapis.com/maps/api/geocode/json?sensor=true&latlng=,so it is server side reverse geocoding as you are calling geocoding api by making an extra External http call.
if this is server-side Geocoding, then can this code only be run 2,500 times per day period, or can it be run 2,500 times daily per user of the app?
2,500 request limit is per IP address(basically it is mentioning 2500 request per day),yah this code only be run 2,500 times per day for all of your user.One thing you should keep in mind you are making http call to geocoder api so it doesn't matter from where you are making this call from server or from client.
you should have a look on this google link where they have mention "When to Use Client-Side Geocoding" and "When to Use Server-Side Geocoding".
I built an android app which can handle a share intent from Google Maps and show it's coordinates.
The problem is that they send a short url which I decode with Google's url shortner api and in some cases, the result long link is of this type: http://maps.google.com/?cid=3635533832900933072&hl=en&gl=us.
Can anyone help me on how to get the coresponding coordinates to "cid=3635533832900933072"
As far as I know there is no public API to get the location from a cid.
However, a possible workaround would be to parse the Google Maps output to obtain the latitude and longitude (though it may be brittle, if they change the result format).
(Although the url contains output=json, it's not actually json -- that's why I parse it with substring() and such instead of using JSONObject).
Try this code:
public static LatLng getCidCoordinates(String cid)
{
final String URL_FORMAT = "http://maps.google.com/maps?cid=%s&q=a&output=json";
final String LATLNG_BEFORE = "viewport:{center:{";
final String LATLNG_AFTER = "}";
final String LATLNG_SEPARATOR = ",";
final String LAT_PREFIX = "lat:";
final String LNG_PREFIX = "lng:";
try
{
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(String.format(URL_FORMAT, cid));
HttpResponse response = client.execute(get);
String text = EntityUtils.toString(response.getEntity(), "UTF-8");
int startIndex = text.indexOf(LATLNG_BEFORE);
if (startIndex == -1)
return null;
startIndex += LATLNG_BEFORE.length();
int endIndex = text.indexOf(LATLNG_AFTER, startIndex);
// Should be "lat:<number>,lng:<number>"
String[] parts = text.substring(startIndex, endIndex).split(LATLNG_SEPARATOR);
if (parts.length != 2)
return null;
if (parts[0].startsWith(LAT_PREFIX))
parts[0] = parts[0].substring(LAT_PREFIX.length());
else
return null;
if (parts[1].startsWith(LNG_PREFIX))
parts[1] = parts[1].substring(LNG_PREFIX.length());
else
return null;
return new LatLng(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]));
}
catch (NumberFormatException e)
{
e.printStackTrace();
return null;
}
catch (IOException e)
{
e.printStackTrace();
return null;
}
}
After reading this post yesterday, I found a new method to do it. I hope Google do not close this new API and hidden parameter. :)
You can use this API hidden parameter to get the coordinater. Usage: https://maps.googleapis.com/maps/api/place/details/json?cid=YOUR_CID&key=YOUR_KEY
It returns a result contains formatted address, place_id, name of the address and GPS coordinater.
Please see my blog to see more detail: https://leonbbs.blogspot.com/2018/03/google-map-cid-to-placeid-or-get.html
In latest Google Maps update, the share intent contains the address name in the body which can be decoded with Geocoder into coordinates.
Currently developing an Android application that returns the closest 20 location to the users current location.
Google Places API is returning ~20 places close to the users location but not the closest 20 sorted by distance.
Looking at the Google Places API Documentation does not show anything that I can see to be incorrect.
GetPlaces.java
String types = "accounting|airport|amusement_park|aquarium|art_gallery|atm|bakery|bank|bar|beauty_salon|bicycle_store|book_store|bowling_alley|bus_station|cafe|campground|car_dealer|car_rental|car_repair|car_wash|casino|cemetery|church|city_hall|clothing_store|convenience_store|courthouse|dentist|department_store|doctor|electrician|electronics_store|embassy|establishment|finance|fire_station|florist|food|funeral_home|furniture_store|gas_station|general_contractor|grocery_or_supermarket|gym|hair_care|hardware_store|health|hindu_temple|home_goods_store|hospital|insurance_agency|jewelry_store|laundry|lawyer|library|liquor_store|local_government_office|locksmith|lodging|meal_delivery|meal_takeaway|mosque|movie_rental|movie_theater|moving_company|museum|night_club|painter|park|parking|pet_store|pharmacy|physiotherapist|place_of_worship|plumber|police|post_office|real_estate_agency|restaurant|roofing_contractor|rv_park|school|shoe_store|shopping_mall|spa|stadium|storage|store|subway_station|synagogue|taxi_stand|train_station|travel_agency|university|veterinary_care|zoo";
resourceURI = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location="+myLocation.latitude+","+myLocation.longitude+"&radius=500&rankBy=distance&types="+ URLEncoder.encode(types, "UTF-8")+"&sensor=true&key=GOOGLE_MAPS_KEY";
try {
String url =resourceURI; //getURL(myLocation.latitude,myLocation.longitude);
HttpParams httpParams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 30000);
HttpConnectionParams.setSoTimeout(httpParams, 30000);
DefaultHttpClient httpClient = new DefaultHttpClient(httpParams);
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Content-type", "application/json");
ResponseHandler responseHandler = new BasicResponseHandler();
String response = (String) httpClient.execute(httpGet, responseHandler);
if (response != null) {
mResult = new JSONObject(response);
results = mResult.getJSONArray("results");
}
}
catch (ClientProtocolException e) {
e.printStackTrace();
return null;
}
catch (IOException e) {
e.printStackTrace();
return null;
}
catch (JSONException e) {
e.printStackTrace();
return null;
}
return results;
}
This returns valid JSON, but not the closest places to the passed in distance. I know for a fact that there are closer places than what the request is returning.
For example, I make a request at a known google place, but it is not showing the place I am currently at- but others that are farther.
Maybe you've already solved your problem, but I hope this can help (Focus on what is in bold):
radius - Defines the distance (in meters) Within Which to return Place results. The maximum allowed is 50 000 meters radius. Note That radius must not be included if rankby = distance (Described below under Optional parameters) is specified.
rankby — Specifies the order in which results are listed. Possible values are:
prominence (default). This option sorts results based on their importance. Ranking will favor prominent places within the specified area. Prominence can be affected by a Place's ranking in Google's index, the number of check-ins from your application, global popularity, and other factors.
distance. This option sorts results in ascending order by their distance from the specified location. When distance is specified, one or more of keyword, name, or types is required.
according: https://developers.google.com/places/documentation/search?hl=en
I have understood according to google documentation that you can not simultaneously send arguments "rankby" and "radius", you must use only one of them at the same time, this way you will get the results sorted by distance.
test the request doing this:
resourceURI = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?
location="+myLocation.latitude+","+myLocation.longitude+"&rankBy=distance
&types="+ URLEncoder.encode (types, "UTF-8") + "&sensor = true&
key = GOOGLE_MAPS_KEY";
to see how you do, good luck!
Hope this code will work..
private ArrayList<CLPlaceDTO> sortLocation(LatLng currentLatLng, ArrayList<?> alLocationDTO)
{
ArrayList<CLPlaceDTO> clPlaceDTOArrayList;
ArrayList<CLPlaceDTO> clPlaceDTOArrayList1 = new ArrayList<>();
double dCurrentLat = currentLatLng.latitude;
double dCurrentLong = currentLatLng.longitude;
Iterator<?> clIterator = alLocationDTO.iterator();
while (clIterator.hasNext())
{
clIterator.next();
clPlaceDTOArrayList = new ArrayList<>();
for (int j = 0; j < alLocationDTO.size(); j++)
{
CLLocationDTO clLocationDTO = (CLLocationDTO) alLocationDTO.get(j);
double dLat = clLocationDTO.getLatitude().doubleValue();
double dLng = clLocationDTO.getLongitude().doubleValue();
LatLng clNewLatLng = new LatLng(dLat, dLng);
double dDistance = getDistance(dCurrentLat, dCurrentLong, dLat, dLng);
CLPlaceDTO clPlaceDTO = new CLPlaceDTO(clLocationDTO.getAccountName(), clNewLatLng, dDistance);
clPlaceDTOArrayList.add(clPlaceDTO);
}
Collections.sort(clPlaceDTOArrayList, new CLSortPlaces(currentLatLng));
dCurrentLat = clPlaceDTOArrayList.get(0).getLatlng().latitude;
dCurrentLong = clPlaceDTOArrayList.get(0).getLatlng().longitude;
clPlaceDTOArrayList1.add(clPlaceDTOArrayList.get(0));
clIterator.remove();
}
return clPlaceDTOArrayList1;
}
public static double getDistance(double dbFromLatitude,double dbFromLongitude,double dbToLatitude,double dbToLongitude)
{
double dbRadiusMeters = EARTH_RADIUS * 1000 ; // Earth’s mean radius in meter
double dbLatitudeDiff = Math.toRadians(dbToLatitude - dbFromLatitude);
double dbLongitudeDiff = Math.toRadians(dbToLongitude - dbFromLongitude);
double a = Math.sin(dbLatitudeDiff / 2) * Math.sin(dbLatitudeDiff / 2) +
Math.cos(Math.toRadians(dbFromLatitude)) * Math.cos(Math.toRadians(dbToLatitude)) *
Math.sin(dbLongitudeDiff / 2) * Math.sin(dbLongitudeDiff / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = dbRadiusMeters * c;
return d; // returns the distance in meter
}
public class CLSortPlaces implements Comparator<CLPlaceDTO>
{
private LatLng currentLoc;
CLSortPlaces(LatLng current)
{
currentLoc = current;
}
#Override
public int compare(final CLPlaceDTO place1, final CLPlaceDTO place2)
{
return (int) (place1.dDistance - place2.dDistance);
}
}
public class CLPlaceDTO
{
public LatLng latlng;
public String sNameOfLocation;
public double dDistance;
public CLPlaceDTO(String sNameOfLocation, LatLng latlng,double dDistance)
{
this.sNameOfLocation = sNameOfLocation;
this.latlng = latlng;
this.dDistance=dDistance;
}
public CLPlaceDTO(String sNameOfLocation, LatLng latlng)
{
this.sNameOfLocation = sNameOfLocation;
this.latlng = latlng;
this.dDistance=dDistance;
}}
It's quite possible that the places that you're thinking are closer than the places that are being returned are not true Google places.
Check out this FAQ for getting your business on Google maps: https://support.google.com/places/answer/145585?hl=en .
A test for this could be to attempt to register the business you think is closer with Google places https://www.google.com/local/business/add?hl=en&migratedId=04614545856067425787 and see if it allows you. If you can, this would mean that they are not a true Google place and would not show up in this result set.
8/5/11: Update near bottom of post
I'm trying to call my WCF web service method from my Android app using kSOAP. The method takes 4 parameters. 3 of them are of type Guid (for which marshalling Java's UUID works), and the last is a custom type: LocationLibrary.Location. This type is in a separate DLL (LocationLibrary) that I load into the WCF web service project and it is basically comprised of two doubles, latitude and longitude.
[OperationContract]
byte[] GetData(Guid deviceId, Guid appId, Guid devKey, LocationLibrary.Location loc);
The Location class in the project LocationLibrary is very simple:
namespace LocationLibrary
{
public class Location
{
public double latitude { get; set; }
public double longitude { get; set; }
public new string ToString()
{
return latitude.ToString() + "," + longitude.ToString();
}
public bool Equals(Location loc)
{
return this.latitude == loc.latitude && this.longitude == loc.longitude;
}
}
}
In my Android project, I've created a class named "Location" that is similar to the .NET version:
public class Location {
public double latitude;
public double longitude;
public Location() {}
public static Location fromString(String s)
{
//Format from SOAP message is "anyType{latitude=39.6572799682617; longitude=-78.9278602600098; }"
Location result = new Location();
String[] tokens = s.split("=");
String lat = tokens[1].split(";")[0];
String lng = tokens[2].split(";")[0];
result.latitude = Double.parseDouble(lat);
result.longitude = Double.parseDouble(lng);
return result;
}
public String toString()
{
return Double.toString(latitude) + "," + Double.toString(longitude);
}
}
When using kSOAP to connect to the web service, I do the following:
private final String SOAP_ACTION = "http://tempuri.org/IMagicSauceV3/GetData";
private final String OPERATION_NAME = "GetData";
private final String WSDL_TARGET_NAMESPACE = "http://tempuri.org/";
private final String SOAP_ADDRESS = "http://mydomain.com/myservice.svc";
private final SoapSerializationEnvelope envelope;
SoapObject request = new SoapObject(WSDL_TARGET_NAMESPACE, OPERATION_NAME);
request.addProperty(Cloud8Connector.deviceIdProperty);
request.addProperty(Cloud8Connector.appIdProperty);
request.addProperty(Cloud8Connector.devKeyProperty);
PropertyInfo locationProperty = new PropertyInfo();
locationProperty.setName("loc");
locationProperty.setValue(Cloud8Connector.myLoc);
locationProperty.setType(Location.class);
request.addProperty(locationProperty);
envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
MarshalUUID mu = new MarshalUUID();
mu.register(envelope);
MarshalLocation ml = new MarshalLocation();
ml.register(envelope);
byte[] result = null;
HttpTransportSE httpRequest = new HttpTransportSE(SOAP_ADDRESS);
try
{
httpRequest.call(SOAP_ACTION, envelope);
String payloadString = ((SoapPrimitive)(envelope.getResponse())).toString();
result = Base64.decode(payloadString, Base64.DEFAULT);
}
catch(Exception e)
{
e.printStackTrace();
}
As you can see, I've created a simple Marshal class for Location, which basically just uses the fromString and toString methods. I register the envelope with the marshal instance for Location:
MarshalLocation ml = new MarshalLocation();
ml.register(envelope);
Here's the marshal class for Location:
public class MarshalLocation implements Marshal
{
public Object readInstance(XmlPullParser parser, String namespace, String name,
PropertyInfo expected) throws IOException, XmlPullParserException {
return Location.fromString(parser.nextText());
}
public void register(SoapSerializationEnvelope cm) {
cm.addMapping(cm.xsd, "Location", Location.class, this);
}
public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
writer.text(((Location)obj).toString());
}
}
However, I get this error returned from WCF and cannot seem to get it to work. From the error and from searching, I think I need to tweak something with my web service, but I'm not sure what exactly is the best way to get around this.
I've tried adding this to the web.config file of the web service:
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="Location, LocationLibrary, Version=1.0.0.0,Culture=neutral,PublicKeyToken=null">
<knownType type="Location, LocationLibrary, Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"></knownType>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
And I've tried adding the ServiceKnownType attribute to the interface method signature:
[OperationContract]
[ServiceKnownType(typeof(LocationLibrary.Location))]
byte[] GetData(Guid deviceId, Guid appId, Guid devKey, LocationLibrary.Location loc);
But I still get this error :(
Can you point me in the right direction? Thanks!
Error:
07-30 00:42:08.186: WARN/System.err(8723): SoapFault - faultcode:
'a:DeserializationFailed' faultstring: 'The formatter threw an
exception while trying to deserialize the message: There was an error
while trying to deserialize parameter http://tempuri.org/:loc. The
InnerException message was 'Error in line 1 position 522. Element
'http://tempuri.org/:loc' contains data from a type that maps to the
name 'http://www.w3.org/2001/XMLSchema:Location'. The deserializer has
no knowledge of any type that maps to this name. Consider using a
DataContractResolver or add the type corresponding to 'Location' to
the list of known types - for example, by using the KnownTypeAttribute
attribute or by adding it to the list of known types passed to
DataContractSerializer.'. Please see InnerException for more details.'
faultactor: 'null' detail: org.kxml2.kdom.Node#4053aa28
Update
Ideally, I wouldn't have to move the LocationLibrary classes inside of the main WCF project since that will complicate handling of legacy clients that expect it in a different namespace. However, I did get things to work by moving those classes inside of the main WCF project AND modifying the marshal class in java to this:
public void register(SoapSerializationEnvelope cm) {
cm.addMapping("http://schemas.datacontract.org/2004/07/MagicSauceV3", "Location", Location.class, this);
}
public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
Location loc = (Location)obj;
writer.startTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "latitude");
writer.text(Double.toString(loc.latitude));
writer.endTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "latitude");
writer.startTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "longitude");
writer.text(Double.toString(loc.longitude));
writer.endTag("http://schemas.datacontract.org/2004/07/MagicSauceV3", "longitude");
}
Now that I got that to work, I just get it working with the LocationLibrary WCF classes how they were (in a separate DLL). So I've modified the marshal class as such:
public void register(SoapSerializationEnvelope cm) {
cm.addMapping("http://schemas.datacontract.org/2004/07/LocationLibrary", "Location", Location.class, this);
}
public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
Location loc = (Location)obj;
writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
writer.text(Double.toString(loc.latitude));
writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");
writer.text(Double.toString(loc.longitude));
writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");
}
This seems like it should succeed since it produces similar XML to the WP7 version that does work.
WP7 working XML request:
<GetData xmlns="http://tempuri.org/">
<deviceId>{valid GUID}</deviceId>
<appId>{valid GUID}</appId>
<devKey>{valid GUID}</devKey>
<loc xmlns:d4p1="http://schemas.datacontract.org/2004/07/LocationLibrary" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:latitude>47.65</d4p1:latitude>
<d4p1:longitude>-122.34</d4p1:longitude>
</loc>
</GetData>
Android non-working XML request:
<GetData xmlns="http://tempuri.org/" id="o0" c:root="1">
<deviceId i:type="d:UUID">{valid GUID}</deviceId>
<appId i:type="d:UUID">{valid GUID}</appId>
<devKey i:type="d:UUID">{valid GUID}</devKey>
<loc i:type="n0:Location" xmlns:n0="http://schemas.datacontract.org/2004/07/LocationLibrary">
<n0:latitude>47.65</n0:latitude>
<n0:longitude>-122.34</n0:longitude>
</loc>
</GetData>
The above Android XML request produces this response error:
08-05 22:51:23.703: WARN/System.err(1382): SoapFault - faultcode: 'a:DeserializationFailed' faultstring: 'The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:loc. The InnerException message was 'Error in line 1 position 590. Element 'http://tempuri.org/:loc' contains data from a type that maps to the name 'http://schemas.datacontract.org/2004/07/LocationLibrary:Location'. The deserializer has no knowledge of any type that maps to this name. Consider using a DataContractResolver or add the type corresponding to 'Location' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.' faultactor: 'null' detail: org.kxml2.kdom.Node#4054bcb8
The only notable difference I see is with the property of "loc" in the working XML request:
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
I don't know if that would make a difference (and it doesn't seem to be referenced with the nested tags). I also don't know how to add that extra namespace without messing up the other one.
Sorry for the long post but I wanted to make sure you have all of the info you need to help :)
Thanks!
Well as I expected you are doing just ToString but it is wrong. Your location is not transported like string of two concatenated values. It is transported as object serialized to XML. Something like:
<Location> <!-- or loc -->
<latitude>10.0</latitude>
<longitude>-20.0</longitude>
</Location>
Thanks to Ladislav for the point in the right direction! I'm not sure what the etiquette is here to reward that, but I'd be glad to follow if someone would advise.
The answer was two-fold, and I can confirm that it now works with the LocationLibrary's Location class in a separate DLL:
Change how I was forming the XML request in Android using my marshaling class. The writeInstance method was only using the .textMethod of the XmlSerializer class, calling .toString() on the Location class. I changed it to utilize the proper way of forming XML tags (.startTag and .endTag methods):
public void writeInstance(XmlSerializer writer, Object obj) throws IOException {
Location loc = (Location)obj;
writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
writer.text(Double.toString(loc.latitude));
writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "latitude");
writer.startTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");
writer.text(Double.toString(loc.longitude));
writer.endTag("http://schemas.datacontract.org/2004/07/LocationLibrary", "longitude");
}
Add some simple tags to the WCF service. I added [DataContract] to the Location class in the LocationLibrary assembly utilized by the WCF service, and I added [DataMember] to each of its members:
[DataContract]
public class Location
{
[DataMember]
public double latitude { get; set; }
[DataMember]
public double longitude { get; set; }
public new string ToString()
{
return latitude.ToString() + "," + longitude.ToString();
}
public bool Equals(Location loc)
{
return this.latitude == loc.latitude && this.longitude == loc.longitude;
}