mapbox-android-demo
mapbox-android-demo copied to clipboard
Location activities report a missing class
All location activities report a missing LocationResult class with each location update:
java.lang.ClassNotFoundException: com.google.android.gms.location.LocationResult
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:454)
at android.os.Parcel.readParcelableCreator(Parcel.java:3014)
at android.os.Parcel.readParcelable(Parcel.java:2964)
at android.os.Parcel.readValue(Parcel.java:2866)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3244)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
at android.os.BaseBundle.unparcel(BaseBundle.java:236)
at android.os.BaseBundle.containsKey(BaseBundle.java:509)
at android.content.Intent.hasExtra(Intent.java:7710)
at com.mapbox.android.core.location.LocationEngineResult.hasResult(LocationEngineResult.java:107)
at com.mapbox.android.core.location.LocationEngineResult.extractAndroidResult(LocationEngineResult.java:101)
at com.mapbox.android.core.location.LocationEngineResult.extractResult(LocationEngineResult.java:92)
at com.mapbox.android.telemetry.location.LocationUpdatesBroadcastReceiver.onReceive(LocationUpdatesBroadcastReceiver.java:35)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1550)
at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.google.android.gms.location.LocationResult" on path: DexPathList[[zip file "/data/app/com.mapbox.mapboxandroiddemo.debug-Kb43cTRDqVHiZeCaTSOWHw==/base.apk"],nativeLibraryDirectories=[/data/app/com.mapbox.mapbo
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:454)
at android.os.Parcel.readParcelableCreator(Parcel.java:3014)
at android.os.Parcel.readParcelable(Parcel.java:2964)
at android.os.Parcel.readValue(Parcel.java:2866)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3244)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
at android.os.BaseBundle.unparcel(BaseBundle.java:236)
at android.os.BaseBundle.containsKey(BaseBundle.java:509)
at android.content.Intent.hasExtra(Intent.java:7710)
at com.mapbox.android.core.location.LocationEngineResult.hasResult(LocationEngineResult.java:107)
at com.mapbox.android.core.location.LocationEngineResult.extractAndroidResult(LocationEngineResult.java:101)
at com.mapbox.android.core.location.LocationEngineResult.extractResult(LocationEngineResult.java:92)
at com.mapbox.android.telemetry.location.LocationUpdatesBroadcastReceiver.onReceive(LocationUpdatesBroadcastReceiver.java:35)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1550)
at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
The activities continue to work as expected, so I suspect a dependency clash.
I've also seen this log cat message in a weekend side project that uses the Maps SDK and shows the device location puck LocationComponent .
cc @harvsu @nkukday in case ya'll have seen this before and/or if this message comes from something upstream from the Maps SDK in https://github.com/mapbox/mapbox-events-android/
I fixed it for me by adding implementation 'com.google.android.gms:play-services-location:17.0.0' into my build.gradle
I fixed it for me by adding
implementation 'com.google.android.gms:play-services-location:17.0.0'into mybuild.gradle
Thx, u save my time !
package com.mapbox.mapboxandroiddemo.examples.dds;
import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PointF; import android.os.AsyncTask; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import android.widget.Toast;
import com.mapbox.geojson.Feature; import com.mapbox.geojson.FeatureCollection; import com.mapbox.mapboxandroiddemo.R; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.annotations.BubbleLayout; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import java.io.InputStream; import java.lang.ref.WeakReference; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List;
import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; import static com.mapbox.mapboxsdk.style.expressions.Expression.get; import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; import static com.mapbox.mapboxsdk.style.layers.Property.ICON_ANCHOR_BOTTOM; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAnchor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOffset;
/**
- Use a SymbolLayer to show a BubbleLayout above a SymbolLayer icon. This is a more performant
- way to show the BubbleLayout that appears when using the MapboxMap.addMarker() method. */ public class InfoWindowSymbolLayerActivity extends AppCompatActivity implements OnMapReadyCallback, MapboxMap.OnMapClickListener {
private static final String GEOJSON_SOURCE_ID = "GEOJSON_SOURCE_ID"; private static final String MARKER_IMAGE_ID = "MARKER_IMAGE_ID"; private static final String MARKER_LAYER_ID = "MARKER_LAYER_ID"; private static final String CALLOUT_LAYER_ID = "CALLOUT_LAYER_ID"; private static final String PROPERTY_SELECTED = "selected"; private static final String PROPERTY_NAME = "name"; private static final String PROPERTY_CAPITAL = "capital"; private MapView mapView; private MapboxMap mapboxMap; private GeoJsonSource source; private FeatureCollection featureCollection;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// Mapbox access token is configured here. This needs to be called either in your application
// object or in the same activity which contains the mapview.
Mapbox.getInstance(this, getString(R.string.access_token));
// This contains the MapView in XML and needs to be called after the access token is configured.
setContentView(R.layout.activity_info_window_symbol_layer);
// Initialize the map view
mapView = findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(this);
}
@Override public void onMapReady(@NonNull final MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; mapboxMap.setStyle(Style.MAPBOX_STREETS, new Style.OnStyleLoaded() { @Override public void onStyleLoaded(@NonNull Style style) { new LoadGeoJsonDataTask(InfoWindowSymbolLayerActivity.this).execute(); mapboxMap.addOnMapClickListener(InfoWindowSymbolLayerActivity.this); } }); }
@Override public boolean onMapClick(@NonNull LatLng point) { return handleClickIcon(mapboxMap.getProjection().toScreenLocation(point)); }
/**
- Sets up all of the sources and layers needed for this example
- @param collection the FeatureCollection to set equal to the globally-declared FeatureCollection */ public void setUpData(final FeatureCollection collection) { featureCollection = collection; if (mapboxMap != null) { mapboxMap.getStyle(style -> { setupSource(style); setUpImage(style); setUpMarkerLayer(style); setUpInfoWindowLayer(style); }); } }
/**
- Adds the GeoJSON source to the map */ private void setupSource(@NonNull Style loadedStyle) { source = new GeoJsonSource(GEOJSON_SOURCE_ID, featureCollection); loadedStyle.addSource(source); }
/**
- Adds the marker image to the map for use as a SymbolLayer icon */ private void setUpImage(@NonNull Style loadedStyle) { loadedStyle.addImage(MARKER_IMAGE_ID, BitmapFactory.decodeResource( this.getResources(), R.drawable.red_marker)); }
/**
- Updates the display of data on the map after the FeatureCollection has been modified */ private void refreshSource() { if (source != null && featureCollection != null) { source.setGeoJson(featureCollection); } }
/**
- Setup a layer with maki icons, eg. west coast city. */ private void setUpMarkerLayer(@NonNull Style loadedStyle) { loadedStyle.addLayer(new SymbolLayer(MARKER_LAYER_ID, GEOJSON_SOURCE_ID) .withProperties( iconImage(MARKER_IMAGE_ID), iconAllowOverlap(true), iconOffset(new Float[] {0f, -8f}) )); }
/**
- Setup a layer with Android SDK call-outs
-
- name of the feature is used as key for the iconImage
/ private void setUpInfoWindowLayer(@NonNull Style loadedStyle) { loadedStyle.addLayer(new SymbolLayer(CALLOUT_LAYER_ID, GEOJSON_SOURCE_ID) .withProperties( / show image with id title based on the value of the name feature property */ iconImage("{name}"),
/* set anchor of icon to bottom-left */
iconAnchor(ICON_ANCHOR_BOTTOM),
/* all info window and marker image to appear at the same time*/
iconAllowOverlap(true),
/* offset the info window to be above the marker */
iconOffset(new Float[] {-2f, -28f})
)
/* add a filter to show only when selected feature property is true */
.withFilter(eq((get(PROPERTY_SELECTED)), literal(true))));
}
/**
- This method handles click events for SymbolLayer symbols.
-
- When a SymbolLayer icon is clicked, we moved that feature to the selected state.
- @param screenPoint the point on screen clicked */ private boolean handleClickIcon(PointF screenPoint) { List<Feature> features = mapboxMap.queryRenderedFeatures(screenPoint, MARKER_LAYER_ID); if (!features.isEmpty()) { String name = features.get(0).getStringProperty(PROPERTY_NAME); List<Feature> featureList = featureCollection.features(); if (featureList != null) { for (int i = 0; i < featureList.size(); i++) { if (featureList.get(i).getStringProperty(PROPERTY_NAME).equals(name)) { if (featureSelectStatus(i)) { setFeatureSelectState(featureList.get(i), false); } else { setSelected(i); } } } } return true; } else { return false; } }
/**
- Set a feature selected state.
- @param index the index of selected feature */ private void setSelected(int index) { if (featureCollection.features() != null) { Feature feature = featureCollection.features().get(index); setFeatureSelectState(feature, true); refreshSource(); } }
/**
- Selects the state of a feature
- @param feature the feature to be selected. */ private void setFeatureSelectState(Feature feature, boolean selectedState) { if (feature.properties() != null) { feature.properties().addProperty(PROPERTY_SELECTED, selectedState); refreshSource(); } }
/**
- Checks whether a Feature's boolean "selected" property is true or false
- @param index the specific Feature's index position in the FeatureCollection's list of Features.
- @return true if "selected" is true. False if the boolean property is false. */ private boolean featureSelectStatus(int index) { if (featureCollection == null) { return false; } return featureCollection.features().get(index).getBooleanProperty(PROPERTY_SELECTED); }
/**
- Invoked when the bitmaps have been generated from a view. */ public void setImageGenResults(HashMap<String, Bitmap> imageMap) { if (mapboxMap != null) { mapboxMap.getStyle(style -> { // calling addImages is faster as separate addImage calls for each bitmap. style.addImages(imageMap); }); } }
/**
- AsyncTask to load data from the assets folder. */ private static class LoadGeoJsonDataTask extends AsyncTask<Void, Void, FeatureCollection> {
private final WeakReference<InfoWindowSymbolLayerActivity> activityRef;
LoadGeoJsonDataTask(InfoWindowSymbolLayerActivity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
protected FeatureCollection doInBackground(Void... params) {
InfoWindowSymbolLayerActivity activity = activityRef.get();
if (activity == null) {
return null;
}
String geoJson = loadGeoJsonFromAsset(activity, "us_west_coast.geojson");
return FeatureCollection.fromJson(geoJson);
}
@Override
protected void onPostExecute(FeatureCollection featureCollection) {
super.onPostExecute(featureCollection);
InfoWindowSymbolLayerActivity activity = activityRef.get();
if (featureCollection == null || activity == null) {
return;
}
// This example runs on the premise that each GeoJSON Feature has a "selected" property,
// with a boolean value. If your data's Features don't have this boolean property,
// add it to the FeatureCollection 's features with the following code:
for (Feature singleFeature : featureCollection.features()) {
singleFeature.addBooleanProperty(PROPERTY_SELECTED, false);
}
activity.setUpData(featureCollection);
new GenerateViewIconTask(activity).execute(featureCollection);
}
static String loadGeoJsonFromAsset(Context context, String filename) {
try {
// Load GeoJSON file from local asset folder
InputStream is = context.getAssets().open(filename);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
return new String(buffer, Charset.forName("UTF-8"));
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
}
/**
- AsyncTask to generate Bitmap from Views to be used as iconImage in a SymbolLayer.
-
- Call be optionally be called to update the underlying data source after execution.
-
- Generating Views on background thread since we are not going to be adding them to the view hierarchy.
*/ private static class GenerateViewIconTask extends AsyncTask<FeatureCollection, Void, HashMap<String, Bitmap>> {
private final HashMap<String, View> viewMap = new HashMap<>();
private final WeakReference<InfoWindowSymbolLayerActivity> activityRef;
private final boolean refreshSource;
GenerateViewIconTask(InfoWindowSymbolLayerActivity activity, boolean refreshSource) {
this.activityRef = new WeakReference<>(activity);
this.refreshSource = refreshSource;
}
GenerateViewIconTask(InfoWindowSymbolLayerActivity activity) {
this(activity, false);
}
@SuppressWarnings("WrongThread")
@Override
protected HashMap<String, Bitmap> doInBackground(FeatureCollection... params) {
InfoWindowSymbolLayerActivity activity = activityRef.get();
if (activity != null) {
HashMap<String, Bitmap> imagesMap = new HashMap<>();
LayoutInflater inflater = LayoutInflater.from(activity);
FeatureCollection featureCollection = params[0];
for (Feature feature : featureCollection.features()) {
BubbleLayout bubbleLayout = (BubbleLayout)
inflater.inflate(R.layout.symbol_layer_info_window_layout_callout, null);
String name = feature.getStringProperty(PROPERTY_NAME);
TextView titleTextView = bubbleLayout.findViewById(R.id.info_window_title);
titleTextView.setText(name);
String style = feature.getStringProperty(PROPERTY_CAPITAL);
TextView descriptionTextView = bubbleLayout.findViewById(R.id.info_window_description);
descriptionTextView.setText(
String.format(activity.getString(R.string.capital), style));
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
bubbleLayout.measure(measureSpec, measureSpec);
float measuredWidth = bubbleLayout.getMeasuredWidth();
bubbleLayout.setArrowPosition(measuredWidth / 2 - 5);
Bitmap bitmap = SymbolGenerator.generate(bubbleLayout);
imagesMap.put(name, bitmap);
viewMap.put(name, bubbleLayout);
}
return imagesMap;
} else {
return null;
}
}
@Override
protected void onPostExecute(HashMap<String, Bitmap> bitmapHashMap) {
super.onPostExecute(bitmapHashMap);
InfoWindowSymbolLayerActivity activity = activityRef.get();
if (activity != null && bitmapHashMap != null) {
activity.setImageGenResults(bitmapHashMap);
if (refreshSource) {
activity.refreshSource();
}
}
Toast.makeText(activity, R.string.tap_on_marker_instruction, Toast.LENGTH_SHORT).show();
}
}
/**
- Utility class to generate Bitmaps for Symbol. */ private static class SymbolGenerator {
/**
* Generate a Bitmap from an Android SDK View.
*
* @param view the View to be drawn to a Bitmap
* @return the generated bitmap
*/
static Bitmap generate(@NonNull View view) {
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(measureSpec, measureSpec);
int measuredWidth = view.getMeasuredWidth();
int measuredHeight = view.getMeasuredHeight();
view.layout(0, 0, measuredWidth, measuredHeight);
Bitmap bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
}
@Override protected void onStart() { super.onStart(); mapView.onStart(); }
@Override public void onResume() { super.onResume(); mapView.onResume(); }
@Override public void onPause() { super.onPause(); mapView.onPause(); }
@Override protected void onStop() { super.onStop(); mapView.onStop(); }
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); }
@Override public void onLowMemory() { super.onLowMemory(); mapView.onLowMemory(); }
@Override protected void onDestroy() { super.onDestroy(); if (mapboxMap != null) { mapboxMap.removeOnMapClickListener(this); } mapView.onDestroy(); } }
Is this still an issue in the latest SDK? I can't simply just add the dependency com.google.android.gms:play-services-location because this causes the getBestProvider method to incorrectly assume play services is installed.