Graphic class explanation required

1232
8
Jump to solution
02-27-2014 09:35 PM
JuliusBagdonas
New Contributor
Documentation states that Graphic objects are immutable, but they appear to have mutators and none of their members are final (see Graphic.class file). Does framework use those setters to change graphic objects? Why do those setters exist?
getSpacialReference() method is one big mystery. There is no way to pass spacial reference via any constructor or method, yet getter exists (it returns null). Where is spacial reference used by the framework and how do I pass it to the graphic? Is it safe to extend Graphic class and override the getter to return my own spacial reference? Is is safe to override any other method and return custom values?
Graphic.toJson() returns empty json string if spacial reference is not set. Again - makes no sense, because there is no way to pass it. Overriding getSpacialReference() and returning a valid spacial reference fixes the problem.
getUid() and getId() always return -1 (regardless of whether or not graphic is in the layer). What are those for?
0 Kudos
1 Solution

Accepted Solutions
EricBader
Occasional Contributor III
I'll take a stab at an explanation.

Graphic is indeed immutable today, but we want to change this implementation at some point down the line when we can.

The reason for this design at the moment:
Graphic needs to be handled in the Core Runtime today. Once its created and passed into the Core, we don't have a live reference to it anymore. It becomes dodgy to try and maintain reference between Core and the Java tier, so here we are.

View solution in original post

0 Kudos
8 Replies
JasonKnisley
Occasional Contributor
I'll take a stab at this, at least in part. Hopefully someone from ESRI can elaborate further.

In the beta version of the SDK (and possibly even in the first few releases?) the Graphic object was mutable. It made a number of things far easier (i.e. manipulating symbology, attributes, etc) and in the other SDKs/APIs that I've worked with the Graphic object is still mutable. I'm sure the Android SDK team had very good reasons to make it immutable, or at least I hope so. Perhaps it was more of a purist decision rather than a practical one, but that is what we as developers are left with. Perhaps the non-final members and package level setters are remnants of it's original, mutable implementation, and now the immutability is ensured based on package level logic rather than being something enforce by the design itself. For example, if you call featureLayer.updateGraphic(graphicUid, myNewGeometry), my guess (based on the immutability claim) is that something like this is really taking place inside the updateGraphic method.

Graphic oldGraphic = getGraphic(graphicUid);
Graphic newGraphic = new Graphic(myNewGeometry, oldGraphic.getSymbol(), oldGraphic.getAttributes());
privateGraphicsList.remove(oldGraphic);
privateGraphicsList.add(newGraphic);


Therefore, behind the scenes the state of the object is never changing because none of the package level classes use the setters. This is, of course, just a guess and obviously means that there is dead code.

I think that early on there may have been a conversation like this...
SDK Developer: "Even though the Graphic object isn't immutable in any of our existing APIs, and wasn't immutable in our beta API, I think we should make it immutable now."
Manager: "Why?"
SDK Developer: "Well, otherwise in our SDK the layers need to be aware of changes to geometry, symbols, attributes, etc for all of the graphics they contain. This is Java so I don't have events and dependency objects to work with. It's really a pain and creates more overhead than I like. Wouldn't it be nice if I could do all of the work when a graphic is added and then never have to worry about it again?"
Manager: "Wouldn't that be a departure from the design patterns employed by our other API/SDK teams?"
SDK Developer: "Huh?"
Manager: "And wouldn't that make life harder on the developers who utilize our SDK to leverage the rest of our product suite?"
SDK Developer: "Who?"

I jest a little here, but the beta SDK was more developer-friendly, as are ESRI's other APIs/SDKs imho. This isn't meant as an insult to the Android SDK team; they've done a fine job and the few members of their team who frequent this forum are both knowledgeable and helpful, and I hope they don't take this post in the wrong way. I don't doubt that they had very good reasons for departing from the standards used by the other teams, I just don't know what those reasons may be, and some things that are very easy in the other APIs (directly accessing a graphic object in a feature layer, manipulating the state of a graphic, etc) are much more tedious in the Android SDK.

Regarding your question about the spatial reference... in some of the other APIs the graphic has a Geometry field, and the Geometry field has a SpatialReference field. So in Silverlight, for example (where the graphic happens to be mutable), if you want to set spatial reference for a graphic it's simply a matter of myGraphic.Geometry.SpatialReference = new SpatialReference(wkid). In Android, for some reason, it doesn't. The graphic's geometry is assumed to be in the same spatial reference as the layer you add it to. It's your responsibility as a developer to ensure that is the case or to reproject is as necessary before adding it to a layer. Why do you need the spatial reference of the graphic? Can you just get the spatial reference of the layer instead? I did a quick test and Graphic.toJson() does work as expected for me (using SDK 10.2.0) without overriding anything. Also, assuming you have multiple graphics you are wanting to convert to JSON, you may want to look at using FeatureSet.toJson() instead.

Regarding your question about getUid() and getId()... I think that has to do with the immutability of the graphic. The graphic that you create manually, before you add it to a layer, isn't going to have a uid because it is logic inside of the GraphicsLayer class that generates the uid. What must be happening, then, is that when you call layer.addGraphic(myGraphic) it determines what the uid should be and then creates a copy of that graphic (with a new reference) using one of the package level constructors such as Graphic(int, Geometry, Symbol, Map<String,Object>). If I were a betting man, I'd bet that the first parameter in that constructor is the uid.

Again, this is part of what makes the Android SDK more painful to work with. Let's say that you have a Graphic array in a FeatureSet returned by a query, and you add those graphics to a layer. The graphics in the layer are NOT the same as the graphics in your FeatureSet. If you want to have an array or list of the actual graphics in your layer, you have to do something like this:

int[] uids = myLayer.getGraphicIDs();
Graphic[] layerGraphics = new Graphic[uids.length];
for (int i = 0, i < uids.length; i++) {
    layerGraphics = myLayer.getGraphic(uid);  // sometimes this is null even though you just got the uids
}


But then again, since the graphics are immutable, it's of questionable use to maintain this array in your own class anyway, because any changes to the graphic will require the new graphic to be created in it's place. Also note with the code above, don't call this until you know that the Graphics have all been loaded in the feature layer. How do you know when that's happened, you ask? Good question. The documentation says that if you set an OnStatusChangedListener then you should see a Status.LAYER_LOADED in onStatusChanged(). If anyone ever sees that, let me know. I've never seen it work.
0 Kudos
JuliusBagdonas
New Contributor
Thank you, that does clarify things a little bit.

I agree that API is somewhat tedious, especially in android-specific areas (e.g. preservation of app state).

By the way, here's the code I used to test Graphic.toJson(). I'm using 10.2 as well.
 public void somethingIsWrong() {
  Point point = new Point(577068, 6056541); // spat ref. wkid 3346
  Symbol greenSymbol = new SimpleMarkerSymbol(Color.GREEN, 20, STYLE.DIAMOND);

  Graphic graphic = new Graphic(point, greenSymbol);
  Graphic customGraphic = new CustomGraphic(point, greenSymbol);
  
  try {
   Log.d(TAG, " graphic --> " + Graphic.toJson(graphic));
   Log.d(TAG, " customGraphic --> " + Graphic.toJson(customGraphic));
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }
 
 public static class CustomGraphic extends Graphic {
  private static final SpatialReference SR_WKID_3346 = SpatialReference.create(3346);

  public CustomGraphic(Geometry geometry, Symbol symbol) {
   super(geometry, symbol);
  }
    
  @Override
  public SpatialReference getSpatialReference() {
   return SR_WKID_3346;
  }
 }

03-01 04:36:03.716: D/DEBUG(21551):  graphic --> {}
03-01 04:36:03.736: D/DEBUG(21551):  customGraphic --> {"geometry":{"x":577068.0,"y":6056541.0,"spatialReference":{"wkid":2600,"latestWkid":3346}}}
0 Kudos
JuliusBagdonas
New Contributor
I took a look inside arcgis-android-api.jar, knisleyj's guess about what's taking place inside GraphicLayer's updateGraphic() method is mostly correct. Extending Graphic makes no sense, getGraphic() returns new and completely different Graphic object. Hopefully in future SDK updates Graphic will actually become immutable (final and without all that dead code). Although after seeing how addGraphic() method works I don't really understand why it should be. Also, why does graphic use Geometry instead of MapGeometry?

Why is this SDK overly complicated...
0 Kudos
EricBader
Occasional Contributor III
I'll take a stab at an explanation.

Graphic is indeed immutable today, but we want to change this implementation at some point down the line when we can.

The reason for this design at the moment:
Graphic needs to be handled in the Core Runtime today. Once its created and passed into the Core, we don't have a live reference to it anymore. It becomes dodgy to try and maintain reference between Core and the Java tier, so here we are.
0 Kudos
JuliusBagdonas
New Contributor
How about using SparseArray to handle references?

Anyway, what about serialization (Graphic.toJson())? Is it a bug? Also MapGeometry vs. Geometry thing...

I don't want to be pesky, but I'm really curious about those... 😄
0 Kudos
JasonKnisley
Occasional Contributor
You are right about the geometry. When I tested Graphic.toJson() it was with a graphic that had attributes, and so when I logged the JSON I got a wall of text and thought it was working, but looking closer it only listed the attributes and not the geometry. There are a few workarounds possible. Once is dumping the graphics into a FeatureSet and then using FeatureSet.toJson(). I have confirmed that doing so results in a JSON string that does contain geometry (albeit without the spatial reference). Alternatively, if all you really care about are geometries because you have no attributes, then you can use GeometryEngine.geometryToJson().

Can I ask what you intend to do with the JSON? If your purpose it to cache the graphics to local storage, you will find that your performance hit will be significant as the number of graphics grows large. An exponentially faster solution for local caching is something like this.

public static boolean writeCachedFeatureSet(FeatureSet featureSet, File cacheFile) {
 cacheFile.getParentFile().mkdirs();
 
 try {
  FileOutputStream fos = new FileOutputStream(cacheFile);
  BufferedOutputStream bos = new BufferedOutputStream(fos);
  ObjectOutputStream output = new ObjectOutputStream(bos);
  output.writeObject(featureSet);
  output.close();
  
  return true;
 } catch (Exception e) {
  // handle exception
 }
 
 return false;
}

public static FeatureSet readCachedFeatureSet(File cacheFile) {
 try {
  FileInputStream fis = new FileInputStream(cacheFile);
  BufferedInputStream bis = new BufferedInputStream(fis);
  ObjectInputStream input = new ObjectInputStream(bis);
  FeatureSet featureSet = (FeatureSet) input.readObject();
  input.close();
  
  return featureSet;
 } catch (Exception e) {
  // handle exception
 }
 
 return null;
}


You can also make use of local geodatabases as of 10.2.0, but all of the related classes are in beta. I'm really looking forward to utilizing those in future apps, but I haven't yet had the inclination to rewrite anything involving local geodatabases so I can't comment on or suggest efficient workflows related to them.
0 Kudos
EricBader
Occasional Contributor III
The Beta will be lifted in just a few days! We are releasing 10.2.2 just be for next week's Developer Summit.
0 Kudos
JuliusBagdonas
New Contributor

Can I ask what you intend to do with the JSON? If your purpose it to cache the graphics to local storage, you will find that your performance hit will be significant as the number of graphics grows large. An exponentially faster solution for local caching is something like this.


Initially the idea was to use Geometry.toJson() to persist Graphic objects between configuration changes and process termination (as is required by Core App Quality Guidelines - FN-S2). I'm using customized geocoding service, so extending Geometry to include all the extra data seemed like a good idea. I started testing serialization (Graphic.toJson()) but that didn't work...
Another way to serialize Graphic into json is to use Jacksons' mixins, but you're correct, Serializable is better...

Also it would be really nice to have Serializable Routes, since they also fall into category of things that should be retained between configuration changes and process termination. Making them (and also Graphic and other data) Parcelable would be even better...

By the way, default Locator and RouteTask could really use cancel() or abort() method, phones usually have relatively awful connections and AsyncTasks.cancel() doesn't cancel anything.
0 Kudos