Below is my current implementation of this method for displaying popups from the latest ArcGIS Runtime for Android SDK, however I have come to a stopping point where you see below:
showPopup(identifiedPopup, screenPoint);
While I have been able to print out popups to the logcat console, I am still unsure how to go about actually configuring their display in my app past this info? I am getting a bit confused looking at the API code for popups here, and am wondering if I need to use the methods mentioned there in order to do this after I grab the popup from my layer?
import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import com.esri.arcgisruntime.concurrent.ListenableFuture; import com.esri.arcgisruntime.data.Feature; import com.esri.arcgisruntime.layers.FeatureLayer; import com.esri.arcgisruntime.mapping.ArcGISMap; import com.esri.arcgisruntime.mapping.GeoElement; import com.esri.arcgisruntime.mapping.popup.Popup; import com.esri.arcgisruntime.mapping.view.DefaultMapViewOnTouchListener; import com.esri.arcgisruntime.mapping.view.IdentifyLayerResult; import com.esri.arcgisruntime.mapping.view.MapView; import com.esri.arcgisruntime.portal.Portal; import com.esri.arcgisruntime.portal.PortalItem; import java.util.List; import java.util.concurrent.ExecutionException; import static com.example.corac.generalglacier.R.id.mapView; public class MainActivity extends AppCompatActivity { private Portal aPortal; private PortalItem aPortalItem; private ArcGISMap aMap; private MapView aMapView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // inflate MapView from layout aMapView = (MapView) findViewById(mapView); // get the portal url for ArcGIS Online aPortal = new Portal(getResources().getString(R.string.portal_url)); // get the pre-defined portal id and portal url aPortalItem = new PortalItem(aPortal, getResources().getString(R.string.webmap_glacier_national_park_id)); // create a map from a PortalItem aMap = new ArcGISMap(aPortalItem); // set the map to be displayed in this view aMapView.setMap(aMap); Log.d("MAP SET", "we have set our map!"); aMapView.setOnTouchListener(new IdentifyFeatureLayerTouchListener(this, aMapView)); Log.d("ID SET", "we have set our map to be identified!"); } } class IdentifyFeatureLayerTouchListener extends DefaultMapViewOnTouchListener { // provide a default constructor public IdentifyFeatureLayerTouchListener(Context context, MapView mapView) { super(context, mapView); } // override the onSingleTapConfirmed gesture to handle a single tap on the MapView @Override public boolean onSingleTapConfirmed(MotionEvent e) { // reference to the layer to identify features in FeatureLayer layer = null; // get the screen point where user tapped final android.graphics.Point screenPoint = new android.graphics.Point((int) e.getX(), (int) e.getY()); // call identifyLayersAsync, passing in the screen point, tolerance, return types, and maximum results, but no layer final ListenableFuture<List<IdentifyLayerResult>> identifyFuture = mMapView.identifyLayersAsync( screenPoint, 10, false); // add a listener to the future identifyFuture.addDoneListener(new Runnable() { @Override public void run() { try { // get the identify results from the future - returns when the operation is complete List<IdentifyLayerResult> identifyLayersResults = identifyFuture.get(); // Here we just show the pop-up for the first (top) layer; if more than one layer may have pop-ups, or more // than one element is identified in a single layer, add controls to the pop-up content to iterate through // multiple pop-ups. if (identifyLayersResults.size() >= 1) { IdentifyLayerResult identifyLayerResult = identifyLayersResults.get(0); // Only identifying the topmost item in this example, so only expecting one pop-up if (identifyLayerResult.getPopups().size() >= 1) { Popup identifiedPopup = identifyLayerResult.getPopups().get(0); showPopup(identifiedPopup, screenPoint); Log.d("popup", identifiedPopup.toString()); } } } catch (InterruptedException | ExecutionException ex) { // deal with exceptions thrown from the async identify operation } } }); return true; } }
Solved! Go to Solution.
Hello, here is the code that worked for me, I followed the feature-layer-show-attributes sample exactly, only implementing it for two layers instead of one:
public class MainActivity extends AppCompatActivity { // existing map references private MapView aMapView; private LocationDisplay mLocationDisplay; private Spinner mSpinner; // request codes from Android private int requestCode = 2; String[] reqPermissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission .ACCESS_COARSE_LOCATION}; // popups private Callout aCallout; private ServiceFeatureTable table0, table1; // private static final String sTag = "Gesture";
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // get the Spinner from layout mSpinner = (Spinner) findViewById(R.id.spinner); // inflate MapView from layout aMapView = (MapView) findViewById(mapView); final ArcGISMap map = new ArcGISMap(Basemap.Type.DARK_GRAY_CANVAS_VECTOR, 48.6596, -113.7870, 9); aMapView.setMap(map);
// get the callout that shows attributes aCallout = aMapView.getCallout(); // layer 0, facilities table0 = new ServiceFeatureTable(getResources().getString(R.string.layer0_url)); FeatureLayer featureLayer0 = new FeatureLayer(table0); map.getOperationalLayers().add(featureLayer0); // get the callout that shows attributes aCallout = aMapView.getCallout(); // layer 1, trails table1 = new ServiceFeatureTable(getResources().getString(R.string.layer1_url)); FeatureLayer featureLayer1 = new FeatureLayer(table1); map.getOperationalLayers().add(featureLayer1); // set an on touch listener to listen for click events aMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, aMapView) { @Override public boolean onSingleTapConfirmed(MotionEvent e) { // remove any existing callouts if (aCallout.isShowing()) { aCallout.dismiss(); } // get the point that was clicked and convert it to a point in map coordinates final Point clickPoint = aMapView.screenToLocation(new android.graphics.Point(Math.round(e.getX()), Math.round(e.getY()))); // create a selection tolerance int tolerance = 10; double mapTolerance = tolerance * aMapView.getUnitsPerDensityIndependentPixel(); // use tolerance to create an envelope to query Envelope envelope = new Envelope(clickPoint.getX() - mapTolerance, clickPoint.getY() - mapTolerance, clickPoint.getX() + mapTolerance, clickPoint.getY() + mapTolerance, map.getSpatialReference()); QueryParameters query = new QueryParameters(); query.setGeometry(envelope); // request all available attribute fields final ListenableFuture<FeatureQueryResult> future_0 = table0.queryFeaturesAsync(query, ServiceFeatureTable.QueryFeatureFields.LOAD_ALL); final ListenableFuture<FeatureQueryResult> future_1 = table1.queryFeaturesAsync(query, ServiceFeatureTable.QueryFeatureFields.LOAD_ALL); //print all available attr fields in logcat System.out.println(future_0.toString()); Log.d("FUTURE0", "see ^"); // example return: com.esri.arcgisruntime.data.ServiceFeatureTable$4@36c3a76 System.out.println(future_1.toString()); Log.d("FUTURE1", "see ^"); // add done loading listener to fire when the selection returns future_0.addDoneListener(new Runnable() { @Override public void run() { try { //call get on the future to get the result FeatureQueryResult result = future_0.get(); // create an Iterator Iterator<Feature> iterator = result.iterator(); // create a TextView to display field values TextView calloutContent = new TextView(getApplicationContext()); calloutContent.setTextColor(Color.BLACK); calloutContent.setSingleLine(false); calloutContent.setVerticalScrollBarEnabled(true); calloutContent.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); calloutContent.setMovementMethod(new ScrollingMovementMethod()); calloutContent.setLines(5); // cycle through selections int counter = 0; Feature feature; while (iterator.hasNext()) { feature = iterator.next(); // create a Map of all available attributes as name value pairs Map<String, Object> attr = feature.getAttributes(); Set<String> keys = attr.keySet(); for (String key : keys) { Object value = attr.get(key); // format observed field value as date if (value instanceof GregorianCalendar) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); value = simpleDateFormat.format(((GregorianCalendar) value).getTime()); } //don't append the null ones if(value.toString() != null | value.toString() != "" | value.toString() != "null") { // append name value pairs to TextView calloutContent.append(key + " | " + value + "\n"); } } counter++; // center the mapview on selected feature Envelope envelope = feature.getGeometry().getExtent(); aMapView.setViewpointGeometryAsync(envelope, 200); // show CallOut aCallout.setLocation(clickPoint); aCallout.setContent(calloutContent); aCallout.show(); } } catch (Exception e) { Log.e(getResources().getString(R.string.app_name), "Select feature failed: " + e.getMessage()); } } }); // add done loading listener to fire when the selection returns future_1.addDoneListener(new Runnable() { @Override public void run() { try { //call get on the future to get the result FeatureQueryResult result = future_1.get(); // create an Iterator Iterator<Feature> iterator = result.iterator(); // create a TextView to display field values TextView calloutContent = new TextView(getApplicationContext()); calloutContent.setTextColor(Color.BLACK); calloutContent.setSingleLine(false); calloutContent.setVerticalScrollBarEnabled(true); calloutContent.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); calloutContent.setMovementMethod(new ScrollingMovementMethod()); calloutContent.setLines(5); // cycle through selections int counter = 0; Feature feature; while (iterator.hasNext()) { feature = iterator.next(); // create a Map of all available attributes as name value pairs Map<String, Object> attr = feature.getAttributes(); Set<String> keys = attr.keySet(); for (String key : keys) { Object value = attr.get(key); // format observed field value as date if (value instanceof GregorianCalendar) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); value = simpleDateFormat.format(((GregorianCalendar) value).getTime()); } //don't append the null ones if(value.toString() != null | value.toString() != "" | value.toString() != "null") { // append name value pairs to TextView calloutContent.append(key + " | " + value + "\n"); } } counter++; // center the mapview on selected feature Envelope envelope = feature.getGeometry().getExtent(); aMapView.setViewpointGeometryAsync(envelope, 200); // show CallOut aCallout.setLocation(clickPoint); aCallout.setContent(calloutContent); aCallout.show(); } } catch (Exception e) { Log.e(getResources().getString(R.string.app_name), "Select feature failed: " + e.getMessage()); } } }); return super.onSingleTapConfirmed(e); } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Location permission was granted. This would have been triggered in response to failing to start the // LocationDisplay, so try starting this again. mLocationDisplay.startAsync(); } else { // If permission was denied, show toast to inform user what was chosen. If LocationDisplay is started again, // request permission UX will be shown again, option should be shown to allow never showing the UX again. // Alternative would be to disable functionality so request is not shown again. Toast.makeText(MainActivity.this, getResources().getString(R.string.location_permission_denied), Toast .LENGTH_SHORT).show(); // Update UI to reflect that the location display did not actually start mSpinner.setSelection(0, true); } } @Override protected void onPause() { super.onPause(); aMapView.pause(); } @Override protected void onResume() { super.onResume(); aMapView.resume(); } }
Hi coracoleman14,
What is the showPopup() method that you have?
I do not see it in the code you provided above.
Thanks,
Alexander
Hi, Thank you very much for your response! I am asking for help with writing the showPopup() method since I'm unsure how to proceed since there is no defined showPopup() method in the sample I'm following.
Ahh, okay. I believe the showPopup() method is left blank on purpose as there is a lot of different ways that a developer could implement this and more than likely, it is left up to the developers design and preference for their applications implementation.
I guess we need to break it down into how do you want to display your popups and specific behavior that you are looking for in order to move forward. The popups can be displayed either as a separate view entirely or in a callout (think similar to ArcGIS Online). What do you envision as the way you are interested in showing the popup?
Then, you would need to determine if you want to show attachments and media within the popups and determine how you would like to display this (again separate view or in the callout).
The code that is displayed above will only show the top selected element that is returned from the popups, this means that if you have multiple features sitting very close to each other (nearly on top of each other if not sitting on top of each other) when clicked will only return one feature. Would you want to handle having multiple features showing? And this again would require some thought on how to handle this. (Perhaps a dialog with a recyclerview).
What type of features are you going to support? (MapServices, FeatureServices, Both?) Are you supporting all webmaps or do you have a specific webmap in mind?
When the above is determined, we can perhaps provide a few pointers in the direction to go with this (along with some small snippets of code).
Did you have a look at my sample that I wrote a couple of months ago? ( GitHub - nohe427/KtPopup: Kotlin Popups )
This is written in Kotlin which is a bit different than Java, but the work involved to get rolling should be similar. Are you familiar with Kotlin? In this sample, I used the umano AndroidSlidingUpPanel (GitHub - umano/AndroidSlidingUpPanel: This library provides a simple way to add a draggable sliding ... ) to display my popups. This was quite a challenge getting everything working correctly and is not something that comes out of the box from the SDK. Since we last talked, I did bring it up to the SDK team that you are interested in seeing a function added for default behavior.
Hello, thank you so much for your response! This I found to be very helpful in thinking about how I want to do the following:
I would want to display popups in a Callout - similar to ArcGIS Online.
I wouldn't want to support media (yet) but am interested in how I would go about for example, referencing data on a webpage within the same Callout as a different field?
I am seeking to support this form of feature for now, referencing a Portal item within a mapView.
Thank you for the other resources you included! I will look to these to get a better understanding.
Hello.Please check following link.It may be helpful for you.
Thank you both so much for your advice, I was able to view my popups in callouts by following the show feature attributes sample! I had to be sure to add and reference both of my feature layers appropriately since I am referencing two!
Hello Cora.
I'm stuck at the same point...
showPopup(identifiedPopup, screenPoint);
How do you defined your popups?
Could you paste your code?
Many thks!
Hello, here is the code that worked for me, I followed the feature-layer-show-attributes sample exactly, only implementing it for two layers instead of one:
public class MainActivity extends AppCompatActivity { // existing map references private MapView aMapView; private LocationDisplay mLocationDisplay; private Spinner mSpinner; // request codes from Android private int requestCode = 2; String[] reqPermissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission .ACCESS_COARSE_LOCATION}; // popups private Callout aCallout; private ServiceFeatureTable table0, table1; // private static final String sTag = "Gesture";
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // get the Spinner from layout mSpinner = (Spinner) findViewById(R.id.spinner); // inflate MapView from layout aMapView = (MapView) findViewById(mapView); final ArcGISMap map = new ArcGISMap(Basemap.Type.DARK_GRAY_CANVAS_VECTOR, 48.6596, -113.7870, 9); aMapView.setMap(map);
// get the callout that shows attributes aCallout = aMapView.getCallout(); // layer 0, facilities table0 = new ServiceFeatureTable(getResources().getString(R.string.layer0_url)); FeatureLayer featureLayer0 = new FeatureLayer(table0); map.getOperationalLayers().add(featureLayer0); // get the callout that shows attributes aCallout = aMapView.getCallout(); // layer 1, trails table1 = new ServiceFeatureTable(getResources().getString(R.string.layer1_url)); FeatureLayer featureLayer1 = new FeatureLayer(table1); map.getOperationalLayers().add(featureLayer1); // set an on touch listener to listen for click events aMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, aMapView) { @Override public boolean onSingleTapConfirmed(MotionEvent e) { // remove any existing callouts if (aCallout.isShowing()) { aCallout.dismiss(); } // get the point that was clicked and convert it to a point in map coordinates final Point clickPoint = aMapView.screenToLocation(new android.graphics.Point(Math.round(e.getX()), Math.round(e.getY()))); // create a selection tolerance int tolerance = 10; double mapTolerance = tolerance * aMapView.getUnitsPerDensityIndependentPixel(); // use tolerance to create an envelope to query Envelope envelope = new Envelope(clickPoint.getX() - mapTolerance, clickPoint.getY() - mapTolerance, clickPoint.getX() + mapTolerance, clickPoint.getY() + mapTolerance, map.getSpatialReference()); QueryParameters query = new QueryParameters(); query.setGeometry(envelope); // request all available attribute fields final ListenableFuture<FeatureQueryResult> future_0 = table0.queryFeaturesAsync(query, ServiceFeatureTable.QueryFeatureFields.LOAD_ALL); final ListenableFuture<FeatureQueryResult> future_1 = table1.queryFeaturesAsync(query, ServiceFeatureTable.QueryFeatureFields.LOAD_ALL); //print all available attr fields in logcat System.out.println(future_0.toString()); Log.d("FUTURE0", "see ^"); // example return: com.esri.arcgisruntime.data.ServiceFeatureTable$4@36c3a76 System.out.println(future_1.toString()); Log.d("FUTURE1", "see ^"); // add done loading listener to fire when the selection returns future_0.addDoneListener(new Runnable() { @Override public void run() { try { //call get on the future to get the result FeatureQueryResult result = future_0.get(); // create an Iterator Iterator<Feature> iterator = result.iterator(); // create a TextView to display field values TextView calloutContent = new TextView(getApplicationContext()); calloutContent.setTextColor(Color.BLACK); calloutContent.setSingleLine(false); calloutContent.setVerticalScrollBarEnabled(true); calloutContent.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); calloutContent.setMovementMethod(new ScrollingMovementMethod()); calloutContent.setLines(5); // cycle through selections int counter = 0; Feature feature; while (iterator.hasNext()) { feature = iterator.next(); // create a Map of all available attributes as name value pairs Map<String, Object> attr = feature.getAttributes(); Set<String> keys = attr.keySet(); for (String key : keys) { Object value = attr.get(key); // format observed field value as date if (value instanceof GregorianCalendar) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); value = simpleDateFormat.format(((GregorianCalendar) value).getTime()); } //don't append the null ones if(value.toString() != null | value.toString() != "" | value.toString() != "null") { // append name value pairs to TextView calloutContent.append(key + " | " + value + "\n"); } } counter++; // center the mapview on selected feature Envelope envelope = feature.getGeometry().getExtent(); aMapView.setViewpointGeometryAsync(envelope, 200); // show CallOut aCallout.setLocation(clickPoint); aCallout.setContent(calloutContent); aCallout.show(); } } catch (Exception e) { Log.e(getResources().getString(R.string.app_name), "Select feature failed: " + e.getMessage()); } } }); // add done loading listener to fire when the selection returns future_1.addDoneListener(new Runnable() { @Override public void run() { try { //call get on the future to get the result FeatureQueryResult result = future_1.get(); // create an Iterator Iterator<Feature> iterator = result.iterator(); // create a TextView to display field values TextView calloutContent = new TextView(getApplicationContext()); calloutContent.setTextColor(Color.BLACK); calloutContent.setSingleLine(false); calloutContent.setVerticalScrollBarEnabled(true); calloutContent.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); calloutContent.setMovementMethod(new ScrollingMovementMethod()); calloutContent.setLines(5); // cycle through selections int counter = 0; Feature feature; while (iterator.hasNext()) { feature = iterator.next(); // create a Map of all available attributes as name value pairs Map<String, Object> attr = feature.getAttributes(); Set<String> keys = attr.keySet(); for (String key : keys) { Object value = attr.get(key); // format observed field value as date if (value instanceof GregorianCalendar) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); value = simpleDateFormat.format(((GregorianCalendar) value).getTime()); } //don't append the null ones if(value.toString() != null | value.toString() != "" | value.toString() != "null") { // append name value pairs to TextView calloutContent.append(key + " | " + value + "\n"); } } counter++; // center the mapview on selected feature Envelope envelope = feature.getGeometry().getExtent(); aMapView.setViewpointGeometryAsync(envelope, 200); // show CallOut aCallout.setLocation(clickPoint); aCallout.setContent(calloutContent); aCallout.show(); } } catch (Exception e) { Log.e(getResources().getString(R.string.app_name), "Select feature failed: " + e.getMessage()); } } }); return super.onSingleTapConfirmed(e); } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Location permission was granted. This would have been triggered in response to failing to start the // LocationDisplay, so try starting this again. mLocationDisplay.startAsync(); } else { // If permission was denied, show toast to inform user what was chosen. If LocationDisplay is started again, // request permission UX will be shown again, option should be shown to allow never showing the UX again. // Alternative would be to disable functionality so request is not shown again. Toast.makeText(MainActivity.this, getResources().getString(R.string.location_permission_denied), Toast .LENGTH_SHORT).show(); // Update UI to reflect that the location display did not actually start mSpinner.setSelection(0, true); } } @Override protected void onPause() { super.onPause(); aMapView.pause(); } @Override protected void onResume() { super.onResume(); aMapView.resume(); } }
By the way, you can ignore this part that I included above, as it does not involve anything from that code sample:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Location permission was granted. This would have been triggered in response to failing to start the // LocationDisplay, so try starting this again. mLocationDisplay.startAsync(); } else { // If permission was denied, show toast to inform user what was chosen. If LocationDisplay is started again, // request permission UX will be shown again, option should be shown to allow never showing the UX again. // Alternative would be to disable functionality so request is not shown again. Toast.makeText(MainActivity.this, getResources().getString(R.string.location_permission_denied), Toast .LENGTH_SHORT).show(); // Update UI to reflect that the location display did not actually start mSpinner.setSelection(0, true); } }