Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

Location activities report a missing class #1222

Open
LukasPaczos opened this issue Sep 24, 2019 · 5 comments
Open

Location activities report a missing class #1222

LukasPaczos opened this issue Sep 24, 2019 · 5 comments
Labels

Comments

@LukasPaczos
Copy link
Contributor

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.

@langsmith
Copy link
Contributor

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/

@botanegg
Copy link

I fixed it for me by adding implementation 'com.google.android.gms:play-services-location:17.0.0' into my build.gradle

@DrunkenDealer
Copy link

I fixed it for me by adding implementation 'com.google.android.gms:play-services-location:17.0.0' into my build.gradle

Thx, u save my time !

@ghost
Copy link

ghost commented Jun 26, 2020

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 features = mapboxMap.queryRenderedFeatures(screenPoint, MARKER_LAYER_ID);
    if (!features.isEmpty()) {
    String name = features.get(0).getStringProperty(PROPERTY_NAME);
    List 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();
}
}

@codeversed
Copy link

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants