0

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
Khash
  • 151
  • 1
  • 12
  • This is not helping, show some of your code. – Ghulam Moinul Quadir Apr 16 '18 at 23:19
  • @GhulamMoinulQuadir I added some code to the question. – Khash Apr 17 '18 at 00:01
  • Just add a limited number of markers and start a reasonable timer and repeat until all added. –  Apr 17 '18 at 00:50
  • @Andy I don't believe that is a good solution. It could create more problems down the road. The number of markers is a dynamic number that keeps increasing over time. (the database is constantly being updated). – Khash Apr 17 '18 at 04:13

1 Answers1

0

try to Create your markers in another thread like this

YourActivity.this.runOnUiThread(new Runnable(){
 public void run(){
//paste your code here.
});
  • I added the onMapReady – Khash Apr 17 '18 at 00:05
  • @Houde: Do you mean I should populate my map with the markers in the MainActivity using this code? Because I cannot use runOnThread in my MarkerLoader – Khash Apr 17 '18 at 00:09
  • I mean it's bad idea to use asynctask witch is simple network operations which do not require downloading a lot of data instead of that you don't need to use it manage your code to pass the list of markers in an appropriate way and use runOnThread in the MainActivity. – Houda Laabidi Apr 17 '18 at 00:41
  • take a look at this https://stackoverflow.com/questions/12797550/android-asynctask-for-long-running-operations?r – Houda Laabidi Apr 17 '18 at 00:44
  • another way to do it https://medium.com/@yossisegev/understanding-activity-runonuithread-e102d388fe93 hope it helps. – Houda Laabidi Apr 17 '18 at 00:52
  • Thanks for the link, it is very interesting. It looks like I should not be using AsyncTask for this operation. Any suggestion of how to do this in the background? I could not use the code your provided, there is no runOnThread function that I can call on the activity from my populateMap() method. The only available one is runOnUiThread – Khash Apr 17 '18 at 00:53
  • Thanks, the only problem with that is the fact that since it is running on the UI thread, it blocks the UI while it is adding the markers – Khash Apr 17 '18 at 01:52