1

This is my first time working with google maps in android.I was able to create a map showing my current user location.

However, I would like to display multiple markers whose data I'm fetching using volley. To do this I'm using a backgroundTask class with a singleton class. The backgroundTask class is the one returning the arrayList of the location data.

For this reason I need to populate my BackgroungTask class with the arrayList before my MapActivity become active and access the arraylist of objects in onCreate but it seems the MapActivity loads very fast and a reference to the arrayList of objects from the BackgroungTask class is always null.

This is the BackgroundTask class which is fetching the data

package com.example.carwashplaces;

import android.content.Context;
import android.widget.Toast;


import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

public class BackgroundTask {
private Context context;
private ArrayList<ModelLocation> locationArrayList = new ArrayList<>();
String json_url = "http://histogenetic-exhaus.000webhostapp.com/location.php";

public BackgroundTask(Context context) {
    this.context = context;
}

public ArrayList<ModelLocation> getLocationArrayList(){
    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest (
            Request.Method.POST,
            json_url,
            null,
            new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) {
                   int count = 0;
                   while (count<response.length()){

                       try {
                           JSONObject jsonObject = response.getJSONObject(count);
                           ModelLocation modelLocation = new ModelLocation(
                                   jsonObject.getString("id"),
                                   jsonObject.getString("name"),
                                   jsonObject.getString("latitude"),
                                   jsonObject.getString("longitude"),
                                   jsonObject.getString("staff"),
                                   jsonObject.getString("comment"),
                                   jsonObject.getString("phone"));
                           locationArrayList.add(modelLocation);
                           count++;

                       } catch (JSONException e) {
                           e.printStackTrace();
                       }
                   }

                }
            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {

            Toast.makeText(context, "Error fetching car wash places...  ", Toast.LENGTH_SHORT).show();
            error.printStackTrace();
        }
    });

    MySingleton.getInstance(context).addToRequestque(jsonArrayRequest);

    return locationArrayList;
   }
}

This is the MapActivity where I want to display the locations.

package com.example.carwashplaces;

import android.content.Intent;
import android.content.IntentSender;
import android.location.Location;
import android.os.Bundle;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;

import com.google.android.libraries.places.api.model.AutocompletePrediction;
import com.google.android.libraries.places.api.model.AutocompleteSessionToken;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.model.TypeFilter;
import com.google.android.libraries.places.api.net.FetchPlaceRequest;
import com.google.android.libraries.places.api.net.FetchPlaceResponse;
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest;
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse;
import com.google.android.libraries.places.api.net.PlacesClient;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.mancj.materialsearchbar.MaterialSearchBar;
import com.mancj.materialsearchbar.adapter.SuggestionsAdapter;
import com.skyfishjy.library.RippleBackground;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class MapActivity extends AppCompatActivity implements OnMapReadyCallback {

private GoogleMap mMap;
private FusedLocationProviderClient mFusedLocationProviderClient;
private PlacesClient placesClient;
private List<AutocompletePrediction> predictionList;
private ArrayList<ModelLocation> locations;
private BackgroundTask backgroundTask;

private Location mLastKnownLocation;
private LocationCallback locationCallback;

private MaterialSearchBar materialSearchBar;
private View mapView;
private Button btnFind;
private RippleBackground rippleBg;

private final float DEFAULT_ZOOM = 18;


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

    //init views
    materialSearchBar = findViewById(R.id.searchBar);
    btnFind = findViewById(R.id.btnFind);
    rippleBg = findViewById(R.id.ripple_bg);

    //an object of backgroung task
    backgroundTask = new BackgroundTask(MapActivity.this);
    locations = new ArrayList<>();

    //getting location data from background task
    locations = backgroundTask.getLocationArrayList();



    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
    mapView = mapFragment.getView();

    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(MapActivity.this);
    Places.initialize(MapActivity.this, "MY_KEY");
    placesClient = Places.createClient(this);
    final AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();

    materialSearchBar.setOnSearchActionListener(new MaterialSearchBar.OnSearchActionListener() {
        @Override
        public void onSearchStateChanged(boolean enabled) {

        }

        @Override
        public void onSearchConfirmed(CharSequence text) {
            startSearch(text.toString(), true, null, true);
        }

        @Override
        public void onButtonClicked(int buttonCode) {
            if (buttonCode == MaterialSearchBar.BUTTON_NAVIGATION){
                //opening or closing a  navigation drawer
            }else if (buttonCode == MaterialSearchBar.BUTTON_BACK){
                materialSearchBar.disableSearch();
            }
        }
    });

    materialSearchBar.addTextChangeListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            FindAutocompletePredictionsRequest predictionsRequest = FindAutocompletePredictionsRequest.builder()
                    .setCountry("ke")
                    .setTypeFilter(TypeFilter.ADDRESS)
                    .setSessionToken(token)
                    .setQuery(s.toString())
                    .build();
            placesClient.findAutocompletePredictions(predictionsRequest).addOnCompleteListener(new OnCompleteListener<FindAutocompletePredictionsResponse>() {
                @Override
                public void onComplete(@NonNull Task<FindAutocompletePredictionsResponse> task) {
                    if (task.isSuccessful()){
                        //predictions found, find out what suggestions we have from google
                        FindAutocompletePredictionsResponse predictionsResponse = task.getResult();
                        if (predictionsResponse != null){
                            predictionList = predictionsResponse.getAutocompletePredictions();
                            //converting predictionList into a list of string
                            List<String> suggestionsList = new ArrayList<>();
                            for (int i = 0; i < predictionList.size(); i++){
                                AutocompletePrediction prediction = predictionList.get(i);
                                suggestionsList.add(prediction.getFullText(null).toString());
                            }
                            //pass suggestion list to our MaterialSearchBar
                            materialSearchBar.updateLastSuggestions(suggestionsList);
                            if (!materialSearchBar.isSuggestionsVisible()){
                                materialSearchBar.showSuggestionsList();
                            }
                        }
                    }
                    else {
                        //some error
                        Log.i("mytag", "prediction fetching task failed");
                    }
                }
            });
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });

    materialSearchBar.setSuggstionsClickListener(new SuggestionsAdapter.OnItemViewClickListener() {
        @Override
        public void OnItemClickListener(int position, View v) {
            // seek the longitude and latitude of that suggestion
            if (position >= predictionList.size()){
                return;
            }
            AutocompletePrediction selectedPrediction = predictionList.get(position);
            String suggestion = materialSearchBar.getLastSuggestions().get(position).toString();
            materialSearchBar.setText(suggestion);

            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    materialSearchBar.clearSuggestions();
                }
            },1000);

            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            if (imm != null){
                imm.hideSoftInputFromWindow(materialSearchBar.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
            }
            String placeId = selectedPrediction.getPlaceId();
            List<Place.Field> placeFields = Arrays.asList(Place.Field.LAT_LNG);// get latitude and longitude of a place

            FetchPlaceRequest fetchPlaceRequest = FetchPlaceRequest.builder(placeId, placeFields).build();
            placesClient.fetchPlace(fetchPlaceRequest).addOnSuccessListener(new OnSuccessListener<FetchPlaceResponse>() {
                @Override
                public void onSuccess(FetchPlaceResponse fetchPlaceResponse) {
                    // place found, update camera Factory
                    Place place = fetchPlaceResponse.getPlace();
                    Log.i("mytag", "place found: "+place.getName());
                    LatLng latLngOfPlace = place.getLatLng();
                    if (latLngOfPlace != null){
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLngOfPlace, DEFAULT_ZOOM));
                    }
                }
            }).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    if (e instanceof ApiException){
                        ApiException apiException = (ApiException) e;
                        apiException.printStackTrace();
                        int statusCode = apiException.getStatusCode();
                        Log.i("mytag", "place not found: " +e.getMessage());
                        Log.i("mytag", "status code: "+statusCode);
                    }
                }
            });

        }

        @Override
        public void OnItemDeleteListener(int position, View v) {

        }
    });

    btnFind.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //get the current location of the marker
            LatLng currentMarkerLocation = mMap.getCameraPosition().target;
            rippleBg.startRippleAnimation();
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    rippleBg.stopRippleAnimation();
                    Toast.makeText(MapActivity.this, "Nearest Car Wash", Toast.LENGTH_SHORT).show();
                }
            },3000);
        }
    });

}
//Called when the map is loaded
@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    mMap.setMyLocationEnabled(true);
    mMap.getUiSettings().setMyLocationButtonEnabled(true);


    if (mapView != null  && mapView.findViewById(Integer.parseInt("1")) != null){
        View locationButton = ((View) mapView.findViewById(Integer.parseInt("1")).getParent()).findViewById(Integer.parseInt("2"));
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) locationButton.getLayoutParams();
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0);
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
        layoutParams.setMargins(0, 0, 40, 180);
    }

    //check if gps is enabled or not and then request user to enable it
    LocationRequest locationRequest = LocationRequest.create();
    locationRequest.setInterval(10000);
    locationRequest.setFastestInterval(5000);
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

    LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(locationRequest);

    SettingsClient settingsClient = LocationServices.getSettingsClient(MapActivity.this);
    Task<LocationSettingsResponse> task = settingsClient.checkLocationSettings(builder.build());

    task.addOnSuccessListener(MapActivity.this, new OnSuccessListener<LocationSettingsResponse>() {
        @Override
        public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
            getDeviceLocation();
            setMarkersToCarWashes();
        }
    });

    task.addOnFailureListener(MapActivity.this, new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            if (e instanceof ResolvableApiException){
                ResolvableApiException resolvable = (ResolvableApiException) e;
                try {
                    resolvable.startResolutionForResult(MapActivity.this, 51);
                } catch (IntentSender.SendIntentException ex) {
                    ex.printStackTrace();
                }
            }
        }
    });

    mMap.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {
        @Override
        public boolean onMyLocationButtonClick() {
            if (materialSearchBar.isSuggestionsVisible())
                materialSearchBar.clearSuggestions();
            if (materialSearchBar.isSearchEnabled())
                materialSearchBar.disableSearch();
            return false;
        }
    });


    setMarkersToCarWashes();

}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 51) {
        if (resultCode == RESULT_OK) {
            getDeviceLocation();
            setMarkersToCarWashes();
        }
    }
}

private void setMarkersToCarWashes() {
    if (locations != null){
        for(int i = 0; i < locations.size(); i++){
            double lati = Double.parseDouble(locations.get(i).getLatitude());
            double longLat = Double.parseDouble(locations.get(i).getLongitude());

            //set markers to car wash places
            mMap.addMarker(new MarkerOptions().position(
                    new LatLng(lati, longLat))
            .title(locations.get(i).getName())
            .snippet(locations.get(i).getComment()));

        }
    }
}

private void getDeviceLocation() {
    mFusedLocationProviderClient.getLastLocation()
            .addOnCompleteListener(new OnCompleteListener<Location>() {
                @Override
                public void onComplete(@NonNull Task<Location> task) {
                    if (task.isSuccessful()){
                        mLastKnownLocation = task.getResult();
                        if (mLastKnownLocation != null){
                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(mLastKnownLocation.getLatitude(), mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                        }else {
                            final LocationRequest locationRequest = LocationRequest.create();
                            locationRequest.setInterval(10000);
                            locationRequest.setFastestInterval(5000);
                            locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
                            locationCallback = new LocationCallback(){
                                @Override
                                public void onLocationResult(LocationResult locationResult) {
                                    super.onLocationResult(locationResult);
                                    if (locationResult == null){
                                        return;
                                    }
                                    mLastKnownLocation = locationResult.getLastLocation();
                                    mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(mLastKnownLocation.getLatitude(), mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                                    mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
                                }
                            };
                            mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, null);
                        }
                    }else {
                        Toast.makeText(MapActivity.this, "Unable to get last Location", Toast.LENGTH_SHORT).show();
                    }
                }
            });
    }
}

And This is the part of the code in MapActivity where I'm trying to get the arraylist from the BackgroundTask class

 //an object of backgroung task
    backgroundTask = new BackgroundTask(MapActivity.this);
    locations = new ArrayList<>();

    //getting location data from background task
    locations = backgroundTask.getLocationArrayList();

How do I display the markers in onMapReady method i.e populate the BackgroundTask before the onMapReady method executes?

evan
  • 5,443
  • 2
  • 11
  • 20
Elias Baya
  • 15
  • 6

1 Answers1

0

This is my approach, based off of this solution from related thread. First let's create an interface to handle the volley's response.

public interface LocationsCallback {
    void onSuccess(ArrayList<ModelLocation> fetchedLocations);
}

Then modify your getLocationArrayList method as follows:

public ArrayList<ModelLocation> getLocationArrayList(final LocationsCallback locationsCallback)

Inside the onResponse method of getLocationArrayList, right at the end, add the callback:

public ArrayList<ModelLocation> getLocationArrayList(final LocationsCallback locationsCallback) {

    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
            Request.Method.POST,
            json_url,
            null,
            new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) {
                    // your code here...

                    // Add this at the end here
                    ArrayList<ModelLocation> locations = locationArrayList;
                    locationsCallback.onSuccess(locations);
                }
                //...

Now assign your MapActivity's locations to the fetched array:

locations = new ArrayList<>();
backgroundTask = new BackgroundTask(MapActivity.this);
backgroundTask.getLocationArrayList(new LocationsCallback() {
    @Override
    public void onSuccess(ArrayList<ModelLocation> fetchedLocations) {
        locations = fetchedLocations;

        // Your other code goes here...
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(MapActivity.this);
        mapView = mapFragment.getView();
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(MapActivity.this);
        Places.initialize(MapActivity.this, "KEY");
        placesClient = Places.createClient(MapActivity.this);
        //...
    }
});

And the markers will now be displayed on the map. See screenshot below. :)

enter image description here

evan
  • 5,443
  • 2
  • 11
  • 20
  • Thanks so much Even let me try it and give you a feedback – Elias Baya Sep 22 '19 at 14:26
  • Thanks so much Evan for answering this.. I have all the changes you have proposed but there seem to be no change from my side, could you just post your updated code so that I can see where I'm getting it wrong... Thank you – Elias Baya Sep 22 '19 at 15:57
  • Please see my edit, I hope it's clearer now sorry, let me know if it works for you now. @EliasBaya – evan Sep 22 '19 at 17:02
  • Thanks so much Evan, it worked pretty well,, God bless you – Elias Baya Sep 22 '19 at 20:44