Search code examples
javaandroidgoogle-directions-api

Problem with DirectionsApiRequest and apiRequest.setCallback


Wrote a method for creating and overlaying a route between two points. The problem is that when it's triggered in normal mode (without breakpoints), it causes NullPointerException or IllegalStateException, but when I run my app in Debug-mode it throws nothing and works correctly. I'm sorry for my english (i'm from Russia) and I apologize for formulation of the question, I'm still not really familiar with stackoverflow.

Here is my method:

    List<com.google.maps.model.LatLng> path;
    DirectionsRoute[] routes;
    DisplayMetrics metricsB = new DisplayMetrics();
    int width = metricsB.widthPixels;
    int heith = metricsB.heightPixels;
    PolylineOptions line = new PolylineOptions();
    LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder();
    String dist;

    public void TravelCost(LatLng startGeoPoint, LatLng stopGeoPoint) throws InterruptedException,
ApiException, IOException {

        GeoApiContext geoApiContext = new GeoApiContext.Builder()
                .apiKey(maps_api_key)
                .build();

        DirectionsApiRequest apiRequest = DirectionsApi.newRequest(geoApiContext);

        apiRequest.origin(new com.google.maps.model.LatLng(startGeoPoint.latitude, startGeoPoint.longitude));
        apiRequest.destination(new com.google.maps.model.LatLng(stopGeoPoint.latitude, stopGeoPoint.longitude));
        apiRequest.mode(TravelMode.DRIVING); //set travelling mode

        apiRequest.setCallback(new com.google.maps.PendingResult.Callback<DirectionsResult>() {
            @Override
            public void onResult(DirectionsResult result) {
                routes = result.routes;
                path = routes[0].overviewPolyline.decodePath();
                dist = routes[0].legs[0].distance.humanReadable;
                for (int i = 0; i < path.size(); i++) {
                    line.add(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
                    latLngBuilder.include(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
                }
            }

            @Override
            public void onFailure(Throwable e) {
                Toast.makeText(MapsActivity.this, "Error!", Toast.LENGTH_SHORT).show();
            }
        });

        line.width(16f).color(R.color.purple_500);
        map.addPolyline(line);
        LatLngBounds latLngBounds = latLngBuilder.build();
        CameraUpdate track = CameraUpdateFactory.newLatLngBounds(latLngBounds, 1080, 1920, 25);
        map.moveCamera(track);
    }

Here are errors from log:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.plukash.travelcost, PID: 19561
    java.lang.IllegalStateException: Could not execute method for android:onClick
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:414)
        at android.view.View.performClick(View.java:7448)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
        at android.view.View.performClickInternal(View.java:7425)
        at android.view.View.access$3600(View.java:810)
        at android.view.View$PerformClick.run(View.java:28305)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:409)
        at android.view.View.performClick(View.java:7448) 
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119) 
        at android.view.View.performClickInternal(View.java:7425) 
        at android.view.View.access$3600(View.java:810) 
        at android.view.View$PerformClick.run(View.java:28305) 
        at android.os.Handler.handleCallback(Handler.java:938) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.lang.IllegalStateException: no included points
        at com.google.android.gms.common.internal.Preconditions.checkState(Unknown Source:29)
        at com.google.android.gms.maps.model.LatLngBounds$Builder.build(Unknown Source:21)
        at com.plukash.travelcost.MapsActivity.TravelCost(MapsActivity.java:305)
        at com.plukash.travelcost.MapsActivity.onMapSearch(MapsActivity.java:142)
        at java.lang.reflect.Method.invoke(Native Method) 
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:409) 
        at android.view.View.performClick(View.java:7448) 
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119) 
        at android.view.View.performClickInternal(View.java:7425) 
        at android.view.View.access$3600(View.java:810) 
        at android.view.View$PerformClick.run(View.java:28305) 
        at android.os.Handler.handleCallback(Handler.java:938) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

This is XML-layout file.

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/bottom_sheet_behavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/LocSearch"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="4"
                android:hint="Куда поедем?"
                android:inputType="text"
                tools:ignore="Autofill,HardcodedText" />


            <Button
                android:id="@+id/search_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="0.5"
                android:onClick="onMapSearch"  <------- OnClick Method
                android:text="Search"
                tools:ignore="HardcodedText" />

        </LinearLayout>

        <fragment
            android:id="@+id/map"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MapsActivity"
            tools:ignore="FragmentTagUsage" />
    </LinearLayout>


    <include layout="@layout/bottom_sheet" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="15dp"
        android:src="@drawable/go_px"
        app:layout_anchor="@+id/bottom_sheet"
        app:layout_anchorGravity="top|end"
        tools:ignore="ContentDescription" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

And here is a On-click method for Search-button that calls for "TravelCost" method.

public void onMapSearch(View view) throws InterruptedException, ApiException, IOException {
        boolean bool = TrafficJam();
        try {
            Button search = findViewById(R.id.search_button);

            String location = locationSearch.getText().toString();
            List<Address> addressList;

            Geocoder geocoder = new Geocoder(this);
            try {
                addressList = geocoder.getFromLocationName(location, 1);

            } catch (IOException e) {
                e.printStackTrace();
                Toast toast = new Toast(this);
                toast.setText("Введите корректный адрес!");
                toast.show();
                return;
            }
            if (addressList != null) {
                Address address = addressList.get(0);
                latLng = new LatLng(address.getLatitude(), address.getLongitude());
                map.addMarker(new MarkerOptions().position(latLng).title("Marker"));
                map.animateCamera(CameraUpdateFactory.newLatLng(latLng));
                //Убирать ли текст в поисковом поле?
                //locationSearch.setText("");
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(search.getWindowToken(),
                        InputMethodManager.HIDE_NOT_ALWAYS);
            } else {
                Toast toast = new Toast(this);
                toast.setText("Введите адрес!");
                toast.show();
            }
        } catch (Exception e) {
            Toast toast = new Toast(this);
            toast.setText("Введите корректный адрес!");
            toast.show();
            return;
        }
        if (checker == 1) {
            startpoint = latLng;
            checker += 1;
            locationSearch.setText("");
            locationSearch.setHint("Куда поедем?");
        } else if (checker == 2) {
            LatLng endpoint = latLng;
            locationSearch.setText("Откуда поедем?");
            checker = 1;
            TravelCost(startpoint, endpoint); <-------------- Method call
        }
    }

UPD

Need to return values from other Thread.

DirectionsRoute[] routes;
    public void TravelCost(LatLng startGeoPoint, LatLng stopGeoPoint) {
        GeoApiContext geoApiContext = new GeoApiContext.Builder()
                .apiKey("AIzaSyA9qY28oZ-4TTdDt1jgxdCKLYh9T8Kh0Ss")
                .build();

        DirectionsApiRequest apiRequest = DirectionsApi.newRequest(geoApiContext);
        apiRequest.origin(new com.google.maps.model.LatLng(startGeoPoint.latitude, startGeoPoint.longitude));
        apiRequest.destination(new com.google.maps.model.LatLng(stopGeoPoint.latitude, stopGeoPoint.longitude));
        apiRequest.mode(TravelMode.DRIVING);
        apiRequest.language("Russian");//set travelling mode


        AppExecutors.getInstance().networkIO().execute(() -> {
            apiRequest.setCallback(new com.google.maps.PendingResult.Callback<DirectionsResult>() {
                @Override
                public void onResult(DirectionsResult result) {
                    routes = result.routes;
<---- **routes here contains values**   
                }

                @Override
                public void onFailure(Throwable e) {
                    Toast.makeText(MapsActivity.this, "Error!", Toast.LENGTH_SHORT).show();
                }
            });
        });
<---- **Here routes is nullarray.**
        List<com.google.maps.model.LatLng> path = routes[0].overviewPolyline.decodePath();   
        dist = routes[0].legs[0].distance.humanReadable;
        for (int i = 0; i < path.size(); i++) {
            line.add(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
            latLngBuilder.include(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
        }

        line.width(16f).color(R.color.purple_500);
        map.addPolyline(line);
        LatLngBounds latLngBounds = latLngBuilder.build();
        CameraUpdate track = CameraUpdateFactory.newLatLngBounds(latLngBounds, 1080, 1920, 25);
        map.moveCamera(track);
    }

Solution

  • Your problem seems to be with the android:onClick the system is not finding the method and it's throwing an error because of it.

    My hypothesis is that this is happening because you are running the app with proguard enabled, proguard may be changing the method name, and this will cause this exception.

    I would advise using a click listener since it's considered to be bad practice to use the android:onClick

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            Button button = findViewById(R.id.search_button);
    
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onMapSearch(v);
                }
            });
    
            // implementation of an onclick listener using a lambda instead
            button.setOnClickListener(v -> onMapSearch(v));
        }
    

    Also, you may consider deleting the view parameter in onMapSearch since it's not used anymore

    EDIT

    To fix the android.os.NetworkOnMainThreadException you need to run the network call outside of the main thread, note that it's not advisable to use AsyncTask since it's has been deprecated for a long time now.

    Worker example

    public class AppExecutors {
    
        private static final Object LOCK = new Object();
        private static AppExecutors sInstance;
        private static Executor processor;
        private final Executor diskIO;
        private final Executor networkIO;
        private final Executor mainThread;
    
        private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread){
            this.diskIO = diskIO;
            this.networkIO = networkIO;
            this.mainThread = mainThread;
        }
    
        public static AppExecutors getInstance(){
            if(sInstance == null){
                synchronized (LOCK){
                    sInstance = new AppExecutors(Executors.newSingleThreadExecutor(),
                            Executors.newFixedThreadPool(3),
                            new MainThreadExecutor());
                }
            }
            return sInstance;
        }
    
        public Executor diskIO() {
            return diskIO;
        }
    
        public Executor networkIO() {
            return networkIO;
        }
    
        public Executor getMainThread() {
            return mainThread;
        }
    
        private static class MainThreadExecutor implements Executor {
            private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
    
            @Override
            public void execute(@NotNull Runnable command) {
                mainThreadHandler.post(command);
            }
        }
    }
    

    Usage

    AppExecutors.getInstance().networkIO().execute(() -> {
        // Network call goes here       
    });