MapBox Android: фрагмент прослушивания местоположения падает при вызове onCreateView - PullRequest
0 голосов
/ 06 марта 2020

Я пытаюсь отследить местоположение устройства и отобразить его на карте MapBox. Я должен использовать фрагмент, чтобы отобразить карту, и он реализован, как показано ниже.

Макет

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:mapbox="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/explore_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".presentation.main.explore.ExploreFragment">

        <com.mapbox.mapboxsdk.maps.MapView
            android:id="@+id/map_view"
            mapbox:layout_constraintBottom_toBottomOf="parent"
            mapbox:layout_constraintEnd_toEndOf="parent"
            mapbox:layout_constraintStart_toStartOf="parent"
            mapbox:layout_constraintTop_toTopOf="parent"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Фрагмент

class MapFragment : Fragment(), OnMapReadyCallback, PermissionsListener {

        private lateinit var binding: FragmentMapBinding

        private lateinit var map: MapboxMap
        private lateinit var permissionsManager: PermissionsManager
        private var locationEngine: LocationEngine? = null
        private val locationCallback = LocationCallback(this)

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            Mapbox.getInstance(requireContext().applicationContext, getString(R.string.mapbox_token))
        }

        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ) {
            binding = DataBindingUtil.inflate(
                inflater,
                R.layout.fragment_explore,
                container,
                false
            )

            binding.lifecycleOwner = this

            return binding.root
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            binding.mapView.onCreate(savedInstanceState)
            binding.mapView.getMapAsync(this)
        }

        override fun onMapReady(mapboxMap: MapboxMap) {
            map = mapboxMap
            mapboxMap.setStyle(Style.MAPBOX_STREETS) { loadedStyle ->
                enableLocation(loadedStyle)
            }
        }

        private fun enableLocation(loadedMapStyle: Style) {
            if (PermissionsManager.areLocationPermissionsGranted(requireActivity())) {
                initLocationComponent(loadedMapStyle)
                initLocationEngine()
            } else {
                permissionsManager = PermissionsManager(this)
                permissionsManager.requestLocationPermissions(requireActivity())
            }
        }

        private fun initLocationComponent(loadedMapStyle: Style) {
            val locationComponent: LocationComponent = map.locationComponent
            val locationComponentActivationOptions = LocationComponentActivationOptions.builder(
                requireActivity(),
                loadedMapStyle
            )
                .useDefaultLocationEngine(false)
                .build()

            locationComponent.activateLocationComponent(locationComponentActivationOptions)
            locationComponent.isLocationComponentEnabled = true
            locationComponent.cameraMode = CameraMode.TRACKING
            locationComponent.renderMode = RenderMode.COMPASS
        }

        private fun initLocationEngine() {
            locationEngine = LocationEngineProvider.getBestLocationEngine(requireActivity())

            val request = LocationEngineRequest.Builder(1000L)
                .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
                .setMaxWaitTime(5000L).build()

            locationEngine!!.requestLocationUpdates(request, locationCallback, getMainLooper())
            locationEngine!!.getLastLocation(locationCallback)
        }

        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }

        override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
            Toast.makeText(requireActivity(), "GRANT NEEDED", Toast.LENGTH_LONG).show()
        }

        override fun onPermissionResult(granted: Boolean) {
            if (granted) {
                map.getStyle { loadedStyle ->
                    enableLocation(loadedStyle)
                }
            } else {
                Toast.makeText(requireActivity(), "NOT GRANTED", Toast.LENGTH_LONG).show()
            }
        }

        private class LocationCallback(
            fragment: ExploreFragment
        ) : LocationEngineCallback<LocationEngineResult> {

            private val fragmentWeakReference: WeakReference<ExploreFragment> = WeakReference(fragment)

            override fun onSuccess(result: LocationEngineResult?) {

                if (fragmentWeakReference.get() == null || result == null || result.lastLocation == null) return

                Log.d("TEST", "LAT: ${result.lastLocation?.latitude}")
                Log.d("TEST", "LONG: ${result.lastLocation?.longitude}")
            }

            override fun onFailure(exception: Exception) {
                if (fragmentWeakReference.get() == null) return

                Log.d("TEST", "ERROR: ${exception.localizedMessage}")
            }
        }

        override fun onResume() {
            super.onResume()
            if (::binding.isInitialized) binding.mapView.onResume()
        }

        override fun onPause() {
            super.onPause()
            if (::binding.isInitialized) binding.mapView.onPause()
        }

        override fun onSaveInstanceState(outState: Bundle) {
            super.onSaveInstanceState(outState)
            if (::binding.isInitialized) binding.mapView.onSaveInstanceState(outState)
        }

        override fun onLowMemory() {
            super.onLowMemory()
            if (::binding.isInitialized) binding.mapView.onLowMemory()
        }

        override fun onDestroyView() {
            super.onDestroyView()
            if (locationEngine != null) locationEngine!!.removeLocationUpdates(locationCallback)
            if (::binding.isInitialized) binding.mapView.onDestroy()
        }
    }

Я пытался следовать примеру из документ и применить его к Fragment. Проблема заключается в том, что когда прослушивание местоположения включено и пользователь пытается выйти из MapFragment, приложение вылетает с ошибкой: java.lang.IllegalStateException: Calling getSourceAs when a newer style is loading/has loaded.

Если я удаляю строку if (:: binding.isInitialized) привязки. mapView.onDestroy () из onDestroyView приложение не создает sh, но я знаю, что удалять эту строку не очень хорошая идея.

Как MapBox newb ie, я понятия не имею, в чем причина , Любая помощь будет оценена.

1 Ответ

1 голос
/ 06 марта 2020

Вы видели https://docs.mapbox.com/android/maps/examples/show-a-users-location-on-a-fragment/? Он показывает, как отобразить LocationComponent в Fragment. Использование SupportMapFragment поможет в управлении жизненным циклом.

Приведенный ниже код представляет собой мое коллаж из https://docs.mapbox.com/android/maps/examples/show-a-users-location-on-a-fragment/ и https://docs.mapbox.com/android/maps/examples/location-change-listening/.

См. Окончательный результат на https://imgur.com/a/fyioFgQ. Тост показывает, потому что onSuccess() стреляет.

package com.mapbox.mapboxandroiddemo.examples.location;

import android.annotation.SuppressLint;
import android.location.Location;
import android.os.Bundle;
import android.widget.Toast;

import com.mapbox.android.core.location.LocationEngine;
import com.mapbox.android.core.location.LocationEngineCallback;
import com.mapbox.android.core.location.LocationEngineProvider;
import com.mapbox.android.core.location.LocationEngineRequest;
import com.mapbox.android.core.location.LocationEngineResult;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.android.core.permissions.PermissionsManager;
import com.mapbox.mapboxandroiddemo.R;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.maps.SupportMapFragment;

import java.lang.ref.WeakReference;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentTransaction;

public class LocationComponentFragmentActivity extends AppCompatActivity implements PermissionsListener {

  private static final long DEFAULT_INTERVAL_IN_MILLISECONDS = 1000L;
  private static final long DEFAULT_MAX_WAIT_TIME = DEFAULT_INTERVAL_IN_MILLISECONDS * 5;
  private MapboxMap mapboxMap;
  private PermissionsManager permissionsManager;
  private LocationEngine locationEngine;
  private LocationComponentFragmentActivityLocationCallback callback =
          new LocationComponentFragmentActivityLocationCallback(this);

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_user_location_map_frag);

    // 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));

    // Create supportMapFragment
    SupportMapFragment mapFragment;
    if (savedInstanceState == null) {

      // Create fragment
      final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

      // Build a Mapbox map
      MapboxMapOptions options = MapboxMapOptions.createFromAttributes(this, null);
      options.camera(new CameraPosition.Builder()
        .target(new LatLng(38.899895, -77.03401))
        .zoom(9)
        .build());

      // Create map fragment
      mapFragment = SupportMapFragment.newInstance(options);

      // Add map fragment to parent container
      transaction.add(R.id.location_frag_container, mapFragment, "com.mapbox.map");
      transaction.commit();
    } else {
      mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentByTag("com.mapbox.map");
    }

    if (mapFragment != null) {
      mapFragment.getMapAsync(new OnMapReadyCallback() {
        @Override
        public void onMapReady(@NonNull MapboxMap mapboxMap) {
          LocationComponentFragmentActivity.this.mapboxMap = mapboxMap;
          mapboxMap.setStyle(Style.OUTDOORS, new Style.OnStyleLoaded() {
            @Override
            public void onStyleLoaded(@NonNull Style style) {
              enableLocationComponent(style);
            }
          });
        }
      });
    }
  }

  /**
   * Set up the LocationEngine and the parameters for querying the device's location
   */
  @SuppressLint("MissingPermission")
  private void initLocationEngine() {
    locationEngine = LocationEngineProvider.getBestLocationEngine(this);

    LocationEngineRequest request = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_IN_MILLISECONDS)
            .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
            .setMaxWaitTime(DEFAULT_MAX_WAIT_TIME).build();

    locationEngine.requestLocationUpdates(request, callback, getMainLooper());
    locationEngine.getLastLocation(callback);
  }


  private static class LocationComponentFragmentActivityLocationCallback
          implements LocationEngineCallback<LocationEngineResult> {

    private final WeakReference<LocationComponentFragmentActivity> activityWeakReference;

    LocationComponentFragmentActivityLocationCallback(LocationComponentFragmentActivity activity) {
      this.activityWeakReference = new WeakReference<>(activity);
    }

    /**
     * The LocationEngineCallback interface's method which fires when the device's location has changed.
     *
     * @param result the LocationEngineResult object which has the last known location within it.
     */
    @Override
    public void onSuccess(LocationEngineResult result) {
      LocationComponentFragmentActivity activity = activityWeakReference.get();

      if (activity != null) {
        Location location = result.getLastLocation();

        if (location == null) {
          return;
        }

        // Pass the new location to the Maps SDK's LocationComponent
        if (activity.mapboxMap != null && result.getLastLocation() != null) {
          activity.mapboxMap.getLocationComponent().forceLocationUpdate(result.getLastLocation());
        }
      }
    }

    /**
     * The LocationEngineCallback interface's method which fires when the device's location can't be captured
     *
     * @param exception the exception message
     */
    @Override
    public void onFailure(@NonNull Exception exception) {
      LocationComponentFragmentActivity activity = activityWeakReference.get();
      if (activity != null) {
        Toast.makeText(activity, exception.getLocalizedMessage(),
                Toast.LENGTH_SHORT).show();
      }
    }
  }


  @SuppressWarnings( {"MissingPermission"})
  private void enableLocationComponent(@NonNull Style loadedMapStyle) {
    // Check if permissions are enabled and if not request
    if (PermissionsManager.areLocationPermissionsGranted(this)) {

      // Get an instance of the LocationComponent.
      LocationComponent locationComponent = mapboxMap.getLocationComponent();

      // Activate the LocationComponent
      locationComponent.activateLocationComponent(
        LocationComponentActivationOptions.builder(this, loadedMapStyle)
                .useDefaultLocationEngine(false)
                .build());

      // Enable the LocationComponent so that it's actually visible on the map
      locationComponent.setLocationComponentEnabled(true);

      // Set the LocationComponent's camera mode
      locationComponent.setCameraMode(CameraMode.TRACKING);

      // Set the LocationComponent's render mode
      locationComponent.setRenderMode(RenderMode.NORMAL);

      initLocationEngine();
    } else {
      permissionsManager = new PermissionsManager(this);
      permissionsManager.requestLocationPermissions(this);
    }
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
  }

  @Override
  public void onExplanationNeeded(List<String> permissionsToExplain) {
    Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
  }

@Override
  protected void onDestroy() {
    super.onDestroy();
    if (locationEngine != null) {
      locationEngine.removeLocationUpdates(callback);
    }
  }

  @Override
  public void onPermissionResult(boolean granted) {
    if (granted) {
      mapboxMap.getStyle(new Style.OnStyleLoaded() {
        @Override
        public void onStyleLoaded(@NonNull Style style) {
          enableLocationComponent(style);
        }
      });
    } else {
      Toast.makeText(this, R.string.user_location_permission_not_granted, Toast.LENGTH_LONG).show();
      finish();
    }
  }
}
...