I know there are similar questions around, but I was not able to find a solid answer, here is my question: Is there any way I can make Markers without a Google Map reference to be stored in an ArrayList (or any other storage), and then just simply add them to my map?
Background: I have an app, that at the moment has around 3,500 markers. Each marker also has a data associated with it (boolean array storing data for each marker which is used to make them visible/invisible based on users interactions). At the moment, I get these marker's location and data using a class that extends AsyncTask. After the loading is finished, then I create my markers using this data on the main Thread. However, this takes some time, and it freezes the UI while the markers are being created and added to the map. I want to do this somehow in the background.
What I have tried so far: I created another class that extends AsyncTask passing in my LocationData and my Google Map object. But I get an error when I try to make the markers in my Async class. I get a Runtime error saying I need to do this on the UI thread.
java.lang.RuntimeException: An error occurred while executing doInBackground()
Caused by: com.google.maps.api.android.lib6.common.apiexception.c: Not on the main thread
I have also thought about just making MarkerOptions object in the background and then use that to create markers in the main thread; however, I cannot add a tag to the MarkerOption, it needs to be added to the marker. In that case, I need to go through all of them again in the main thread just to add the tag, which I feel like is not saving me too much time/resources.
Any suggestion/help would be appreciated on how to create these markers and attach their tags without blocking the UI?
Thanks in advance.
Here are some of my code:
LocationLoader class (BinLocation is my Location class, each object has boolean variables (marker tags) and LatLng) public class LocationLoader extends AsyncTaskLoader> {
private String TAG = LocationLoader.class.getName();
String[] fileNameArray;
//ArrayLists
private ArrayList<BinLocation> mBinLocationArrayList = new ArrayList<>();
public LocationLoader(Context context, String... fileNames){
super(context);
//get the file names that was passed in
fileNameArray = fileNames;
}//LocationLoader
@Override
protected void onStartLoading() {
Log.v(TAG, "onStartLoading called");
forceLoad();
}//onStartLoading
@Override
public ArrayList<BinLocation> loadInBackground() {
Log.v(TAG, "loadInBackground called");
String path = "/storage/emulated/0/";
File file;
String output = "";
//Read data from file
for (int i = 0; i < fileNameArray.length; i++) {
file = new File(path + fileNameArray[i]);
try (Scanner scanner = new Scanner(file)) {
//first line of the text, containing the location and version
output = scanner.nextLine();
String prefix = (output.split(":"))[0];
String line;
while (scanner.hasNextLine()) {
line = scanner.nextLine();
String inputArray[] = line.split(",");
BinLocation binLocation = new BinLocation(
prefix + "-" + inputArray[0],
Double.parseDouble(inputArray[1]),
Double.parseDouble(inputArray[2]),
Integer.parseInt(inputArray[3].trim()),
Integer.parseInt(inputArray[4].trim()),
Integer.parseInt(inputArray[5].trim()),
Integer.parseInt(inputArray[6].trim()));
mBinLocationArrayList.add(binLocation);
}//while
} catch (Exception e) {
Log.e(TAG, "File read error: ", e);
}
}//for
Log.v(TAG, "readLocation finished");
Log.v(TAG, "ArrayList size: " + mBinLocationArrayList.size());
return mBinLocationArrayList;
}//loadInBackground
}//LocationLoader class
Here is my MarkerLoader class (I have tried this and got the doInBackground() error). Also there is no code right now here for adding the data to the marker but it foes in the loop right after it has been added to the map.
public class MarkerLoader extends AsyncTaskLoader<ArrayList<Marker>> {
private GoogleMap map;
private ArrayList<Marker> mMarkerArrayList = new ArrayList<>();
private ArrayList<MyLocation> mBinLocationArrayList = new ArrayList<>();
public MarkerLoader (Context context, GoogleMap map, ArrayList<BinLocation> binLocationArrayList) {
super(context);
this.map = map;
this.mBinLocationArrayList = binLocationArrayList;
}//MarkerLoader
@Override
protected void onStartLoading() {
Log.v(TAG, "onStartLoading called");
forceLoad();
}//onStartLoading
@Override
public ArrayList<Marker> loadInBackground() {
Log.v(TAG, "loadInBackground called");
Marker marker;
for (BinLocation binLocation : mMyLocationArrayList) {
marker = map.addMarker(new MarkerOptions()
.position(binLocation.getPosition()));
mMarkerArrayList.add(marker);
}
Log.v(TAG, "loadInBackground finished, with: " + mMarkerArrayList.size());
return mMarkerArrayList;
}
}
This is the helper function(populateMap()) in the main Activity that makes the markers and save them in an ArrayList
private void populateMap() {
if (!checkMapReady() || !mMapIsEmpty) {
return;
}//if Map Not ready
//Initialize ArrayList
mMarkerArrayList = new ArrayList<>();
/**
* This part uses the loop to go through each MyLocation object in the ArrayList, extract
* all the data, and set the markers
*/
//Check to make sure the BinLocation ArrayList is not empty otherwise we will crash
if (mBinLocationArrayList.isEmpty()) {
Log.w(TAG, "populateMap() terminated, mBinLocationArrayList empty");
return;
}//if BinLocation empty
//Safety check to clear the map before populating it
mMap.clear();
//create a markerMyLocation object
Marker mMaker;
//This goes through the ArrayList for every MyLocation object and sets up the markerMyLocation
for (BinLocation binLocation : mBinLocationArrayList) {
//get boolean values
boolean[] booleanValues = {binLocation.getGarbage(), binLocation.getContainer(),
binLocation.getPaper(), binLocation.getCompost()};
//Set different icon
switch (markerIconPreference) {
case "customIcon":
//custom icon
//Decide what icon to use
if (booleanValues[0] && !booleanValues[1] && !booleanValues[2] && !booleanValues[3]) {
//Make a new MarkerOptions object to add the data
//garbage markers
mMaker = mMap.addMarker(new MarkerOptions()
.title(binLocation.getId())
.position(binLocation.getPosition())
.icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_garbage))
.visible(garbageVisible));
} else {
//Make a new MarkerOptions object to add the data
//recycling markers
mMaker = mMap.addMarker(new MarkerOptions()
.title(binLocation.getId())
.position(binLocation.getPosition())
.icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_recycling))
.visible(recyclingVisible));
}
//Add our boolean array as an object to our markerMyLocation
mMaker.setTag(booleanValues);
//Add the markerMyLocation to the ArrayList
mMarkerArrayList.add(mMaker);
break;
case "coloredTeardrop":
//teardrop icon
//Decide what icon to use
if (booleanValues[0] && !booleanValues[1] && !booleanValues[2] && !booleanValues[3]) {
//Make a new MarkerOptions object to add the data
//garbage markers
mMaker = mMap.addMarker(new MarkerOptions()
.title(binLocation.getId())
.position(binLocation.getPosition())
.visible(garbageVisible));
} else {
//Make a new MarkerOptions object to add the data
//recycling markers
mMaker = mMap.addMarker(new MarkerOptions()
.title(binLocation.getId())
.position(binLocation.getPosition())
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))
.visible(recyclingVisible));
}
//Add our boolean array as an object to our markerMyLocation
mMaker.setTag(booleanValues);
//Add the markerMyLocation to the ArrayList
mMarkerArrayList.add(mMaker);
break;
}//switch
}//for
//disable the progress bar
mProgressBar.setVisibility(View.GONE);
//De-activate the CountDown timer since the map is ready
mCountDownTimer.cancel();
//set the boolean to false
mMapIsEmpty = false;
Log.v(TAG, "populateMap finished. Markers: " + mMarkerArrayList.size());
}//populateMap
Here is the onMapReady function
public void onMapReady(GoogleMap map) {
Log.v(TAG, "onMapReady called");
mMap = map;
//Setup on map loaded
mMap.setOnMapLoadedCallback(this);
//Check to see if the map is empty and the location array list is not empty and then call populateMap
if (mMapIsEmpty && !mBinLocationArrayList.isEmpty()) {
populateMap();
}//if map empty
//set bounds
mMap.setLatLngBoundsForCameraTarget(GREATER_VANCOUVER_BOUND);
//Set min zoom level to match the bound
mMap.setMinZoomPreference(10.0f);
//disable map toolbar
UiSettings mUiSettings = mMap.getUiSettings();
mUiSettings.setMapToolbarEnabled(false);
//Set listeners
mMap.setOnMarkerClickListener(this);
mMap.setOnInfoWindowCloseListener(this);
mMap.setOnInfoWindowClickListener(this);
// Setting our custom info window, passing out helper method
mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter());
//Here we check for permission and setup the map accordingly
if (!checkLocationPermission()) {
//Permission is not granted, log, and use the default location
Log.v(TAG, "No location permission");
//setup default map
defaultMapSetup();
} else {
Log.v(TAG, "Location permission granted");
//Enable my location and initialize the map there
mMap.setMyLocationEnabled(true);
//Setup the map
locationMapSetup();
}//if -permission
}//onMapReady