0

I am having trouble with my code where i have my Activity which i use to call google api's and retrieve jsons, deserialize them and use it's Polylines to draw on the map.

The problem is that getMapAsync() which sends the callback for onMapReady() (which is used to create the map) is executed immediately after executing my Async Tasks which retrieves necessary data to create the map.

How can i make this happen without stopping the UI thread? I tried doing this calling .execute.get() which freeze the UI thread. But if i do that, i won't be able to use ProgressDialog to inform the users about the delay for fetching data from the servers, which they will be exposed to a frozen UI until the task is complete. How can i do this?

public class RouteAssistantActivity extends Activity implements OnMapReadyCallback{

public GoogleMapsDirectionsResponse dirRes;
public GoogleMapsDistanceResponse disRes;

public String jsonString;
private String mapsAPIKey;
private String directionsBaseURL;
private String distanceBaseURL;

MapFragment mapFragment;
private ProgressDialog progress;

public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_ra_route_assisstant);

    mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.ra_map);
    progress = new ProgressDialog(RouteAssistantActivity.this);
    progress.setTitle("Please Wait");
    progress.setMessage("Retrieving Data from the Server");
    progress.setIndeterminate(true);

    try {
        ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);

        if (appInfo.metaData != null) {
            mapsAPIKey = appInfo.metaData.getString("com.google.android.maps.v2.API_KEY");
            directionsBaseURL = appInfo.metaData.getString("com.google.android.maps.directions.baseURL");
            distanceBaseURL = appInfo.metaData.getString("com.google.android.maps.distance.baseURL");
        }

    } catch (PackageManager.NameNotFoundException e) {
        Log.e("Meta Error", "Meta Data not found. Please check the Manifest and the Meta Data Package Names");
        e.printStackTrace();
    }

    //Test
    String directionsURL = directionsBaseURL+"origin=6.948109,79.858191&destination=6.910176,79.894347&key="+mapsAPIKey;
    String distanceURL = distanceBaseURL+"units=metric&origins=6.948109,79.858191&destinations=6.910176,79.894347&key="+mapsAPIKey;

    Log.e("CA Debug","URL : " + directionsURL);
    Log.e("CA Debug","URL : " + distanceURL);

    new configurationSyncTask().execute(distanceURL,"distance");
    new configurationSyncTask().execute(directionsURL, "direction");

    mapFragment.getMapAsync(this);

}

@Override
public void onMapReady(GoogleMap googleMap) {
    LatLng rajagiriya = new LatLng(6.910176, 79.894347);

    String points = dirRes.getRoutes().get(0).getOverviewPolyline();
    List<LatLng> list = PolyUtil.decode(points);

    googleMap.setMyLocationEnabled(true);
    googleMap.getUiSettings().setRotateGesturesEnabled(true);
    googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(rajagiriya, 13));

    googleMap.addMarker(new MarkerOptions()
            .title("Rajagiriya")
            .snippet("My Place")
            .position(rajagiriya));

    googleMap.addPolyline(new PolylineOptions()
            .geodesic(false)
            .addAll(list)
            .color(Color.RED)
            .width(25));
}

private class configurationSyncTask extends AsyncTask<String, Void, String> {

    @Override
    protected void onPreExecute() {
        progress.show();
    }

    @Override
    protected String doInBackground(String... params) {

        String url = params[0];
        String type = params[1];

        Log.d("CA Debug", getClass().getSimpleName() + " --> Real URL : " + url);
        Log.d("CA Debug", getClass().getSimpleName() + " --> doInBackground requesting content");

        jsonString = requestContent(url);

        // if the output is null, stop the current task
        if (jsonString == null) {
            Log.d("CA Debug", getClass().getSimpleName() + " --> Stopping Async Task");
            this.cancel(true);
            Log.d("CA Debug", getClass().getSimpleName() + " --> Async Task Stopped");
        }

        return type;
    }

    @Override
    protected void onPostExecute(String types) {

        if (types.equalsIgnoreCase("distance")) {
            disRes = GMapsDistanceResponseJSONDeserializer.deserialize(jsonString);
        } if (types.equalsIgnoreCase("directions")) {
            dirRes = GMapsDirectionsResponseJSONDeserializer.deserialize(jsonString);
        }

        progress.dismiss();
    }


}

public String requestContent(String url) {

    Log.d("CA Debug",getClass().getSimpleName()+" --> URL : "+url);

    try {
        URL urlObj = new URL(url);
        HttpsURLConnection con = (HttpsURLConnection) urlObj.openConnection();
        con.setChunkedStreamingMode(0);
        con.setRequestMethod("GET");

        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null,null, new SecureRandom());
        con.setSSLSocketFactory(sc.getSocketFactory());

        InputStream clientResponse;
        String jsonString;
        int status = con.getResponseCode();

        if(status >= HttpURLConnection.HTTP_BAD_REQUEST){
            Log.d("CA Debug", getClass().getSimpleName()+" --> Bad Request");
            jsonString = null;
        } else {
            Log.d("CA Debug", getClass().getSimpleName()+" --> converting Stream To String");
            clientResponse = con.getInputStream();
            jsonString = convertStreamToString(clientResponse);
        }

        Log.d("CA Debug", getClass().getSimpleName()+" --> JSON STRING : " + jsonString);

        return jsonString;
    } catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {

        Log.d("CA Debug", getClass().getSimpleName()+" --> Error when creating an Input Stream");
        e.printStackTrace();

    }
    return null;
}

public String convertStreamToString(InputStream is) {
    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    StringBuilder sb = new StringBuilder();
    String line = null;

    try {
        while ((line = reader.readLine()) != null) {
            sb.append(line + "\n");
        }
    } catch (IOException e) {
    } finally {
        try {
            is.close();
        } catch (IOException e) {
        }
    }

    return sb.toString();
}

}
k9yosh
  • 858
  • 1
  • 11
  • 31
  • do you have issue with configurationSyncTask().why are you calling configurationSyncTask() twise? – darwin Aug 16 '16 at 09:33
  • @darwin i'm passing two different parameters. parameters are URLs which leads to different APIs. Is there a better way to do it? Suggestions are welcome. – k9yosh Aug 16 '16 at 09:35
  • Execute tasks after onMapReady() completed ? – blackkara Aug 16 '16 at 09:37
  • And these tasks are not parallel. Second task will start after first one completed. – blackkara Aug 16 '16 at 09:39
  • @k9yosh Do you want mapFragment.getMapAsync(this); to work only after completing these two statements ? new configurationSyncTask().execute(distanceURL,"distance"); new configurationSyncTask().execute(directionsURL, "direction"); – Sreehari Aug 16 '16 at 09:43
  • @Blackkara but is there a way to update map later then? And i thought AsyncTasks create separate threads from the main thread as well as the child threads. – k9yosh Aug 16 '16 at 09:45
  • @Stallion yeah exactly, cause i have the Polyline data fetched in those two statements which i need to use when creating the Map with the onMapReady() callback. – k9yosh Aug 16 '16 at 09:48
  • if u want to draw route after getting data from server,move your code to post execute of your async task rather than onMapReady().bcz u can update map ata any time after map got ready. – darwin Aug 16 '16 at 09:50
  • @darwin thanks, i'll try and let you know – k9yosh Aug 16 '16 at 10:04
  • @k9yosh I have formatted your solution. check if it works – Sreehari Aug 16 '16 at 11:08
  • @Stallion thanks, i'll try it and get back to you. – k9yosh Aug 16 '16 at 11:09

3 Answers3

3

Quick and somewhat dirty solution would be to execute both AsyncTasks on a single AsyncTask and then on its onPostExecute code invoke getMapAsync. this way you will be sure your tasks finished before you dealing with map's readyness.

Ofek Ron
  • 8,354
  • 13
  • 55
  • 103
  • i'll give this a try and let you know. Thanks. – k9yosh Aug 16 '16 at 09:56
  • Is it possible to execute an async task in a nested manner? It says that the execute() should be called in the main thread. – k9yosh Aug 16 '16 at 10:43
  • This isn't working. I can only invoke worker threads from the main threads. So even if i create new worker threads inside the previous worker thread i will have to call the execute() method inside the onPostExecute() which gives back the main UI thread. So eventually after executing these two new worker threads, it will immediately execute the getMapAsync() without waiting on those two new threads which leads to a NullPointerException like in my original case. Anyway thanks for helping out. – k9yosh Aug 16 '16 at 11:33
  • You can use the code in the task's callbacks on a single task's callbacks (by copying the actual work to a function and using it twice with diffrent params), just redesign your code for this to work. – Ofek Ron Aug 16 '16 at 11:38
0
  • Firstly, run tasks after onMapReady because you will get rid of concern of ready map.
  • Your async tasks are not parallel, they working on background but second one will be executed after first one completed, check this link
  • Move some parts of onMapReady to onPostExecute, something like below

Move

  @Override
  protected void onPostExecute(String types) {
      if (types.equalsIgnoreCase("distance")) {
          disRes = GMapsDistanceResponseJSONDeserializer.deserialize(jsonString);
      }if (types.equalsIgnoreCase("directions")) {
          dirRes = GMapsDirectionsResponseJSONDeserializer.deserialize(jsonString);
          String points = dirRes.getRoutes().get(0).getOverviewPolyline();
          List<LatLng> list = PolyUtil.decode(points);
          googleMap.addPolyline(new PolylineOptions()
             .geodesic(false)
             .addAll(list)
             .color(Color.RED)
             .width(25)
          );
      }
  }
Community
  • 1
  • 1
blackkara
  • 4,900
  • 4
  • 28
  • 58
0

AsyncTask.SERIAL_EXECUTOR is used to force AsyncTask to execute in Serial Fashion.

More over for your case a little trick will do the job.

  1. Create call back for same AsyncTask and pass different parameters to differentiate the functions.

  2. Now in the second call back initiate mapFragment.getMapAsync(this);


public class MainFragment ...
{
    DataDownloader dataDownloader;
    int processCount=1;


    void initiateProcessFirst(){
        new DataDownloader(this,processCount ).execute();
    }
    public void initiateSecondProcess(){
        processCount++;
        new DataDownloader(this,processCount ).execute();
    }


     public void secondProcessCompleted(){
            mapFragment.getMapAsync(this);
     }
  }

AsyncTask Logic goes like the below

public class DataDownloader extends AsyncTask<Void,Void,Boolean> {

    MainFragment context;
    int processCount;
    public DataDownloader(MainFragment context ,int processCount){
        this.context=context;
        this.processCount=processCount;

    }
    @Override
    protected Boolean doInBackground(Void... params) {
        boolean status=false;
        // Do logic according to the Process Count

        return status;
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
        if(processCount==1)
            context.initiateSecondProcess();
        else
            context.secondProcessCompleted();
    }
}
Sreehari
  • 5,621
  • 2
  • 25
  • 59