简体   繁体   中英

How to get data back from an okhttp call?

I have a method which calls an external API using okhttp library on android, I'm able to access the data that comes back inside that method/thread but I'm not able to return the data or use it somewhere else. What's the problem?

I have tried putting the data in another class (extended from AsyncTask) and it still didn't work.

public class DisplayImage extends AppCompatActivity {

    ImageView imageView;
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_display_image);
        imageView = findViewById(R.id.mImageView);
        textView = findViewById(R.id.textView);


        Bitmap bitmap = BitmapFactory.decodeFile(getIntent().getStringExtra("image_path"));
        imageView.setImageBitmap(bitmap);

        String imagePath = getIntent().getStringExtra("image_path");

        try {
            //map returned here
            HashMap<String, double[]> map = getCropInfo(imagePath);

            //This text view doesn't update
            textView.setText(String.valueOf(map.get("ID")[0]));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    HashMap getCropInfo(String imageUri) throws Exception {
        final HashMap<String, double[]> map = new HashMap<>();

        OkHttpClient client = new OkHttpClient();

        MediaType MEDIA_TYPE_PNG = MediaType.parse("image/jpg");

        File file = new File(imageUri);

        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("image", file.getName(), RequestBody.create(MEDIA_TYPE_PNG, file))
                .build();

        Request request = new Request.Builder()
                .header("Prediction-Key", "") //predictionkey hidden
                .header("Content-Type", "application/octet-stream")
                .url("https://westeurope.api.cognitive.microsoft.com/customvision/v3.0/Prediction/7f5583c8-36e6-4598-8fc3-f9e7db218ec7/detect/iterations/Iteration1/image")
                .post(requestBody)
                .build();

        client.newCall(request).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            public void onResponse(Call call, final Response response) throws IOException {
                // Read data on the worker thread
                final String responseData = response.body().string();

                // Run view-related code back on the main thread
                DisplayImage.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            JSONObject jsonObject = new JSONObject(responseData);
                            JSONArray jsonArray = jsonObject.getJSONArray("predictions");
                            double highestIDProbability = 0;
                            double highestVoltageProbability = 0;

                            for (int i = 0; i < jsonArray.length(); i++) {
                                JSONObject tempObject = jsonArray.getJSONObject(i);
                                if(tempObject.getString("tagName").equals("ID") && tempObject.getDouble("probability") > highestIDProbability) {
                                    highestIDProbability = tempObject.getDouble("probability");
                                    map.put("ID", getCoordinatesPixels(tempObject));
                                }
                                else if(tempObject.getString("tagName").equals("Voltage") && tempObject.getDouble("probability") > highestVoltageProbability) {
                                    highestVoltageProbability = tempObject.getDouble("probability");
                                    map.put("Voltage", getCoordinatesPixels(tempObject));
                                }
                            }
                            //setting text view works from here.
                            //textView.setText(String.valueOf(map.get("ID")[0]));
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        });
        //I am returning map
        return map;
    }

    static double[] getCoordinatesPixels(JSONObject object) {
        double[] arr = new double[4];
        try {
            JSONObject innerObject = object.getJSONObject("boundingBox");
            arr[0] = innerObject.getDouble("left");
            arr[1] = innerObject.getDouble("top");
            arr[2] = innerObject.getDouble("width");
            arr[3] = innerObject.getDouble("height");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return arr;
    }

}

I need the map to return so I can use the data externally.

I believe you're running into an issue related to the asynchronous nature of OkHttp and network requests in general. When you do a new call, that call is queued and handled asynchronously. This means that the code will most likely execute return map; before the asynchronous call has completed and before the callback modifies the map. If you need access to the map outside of the scope of the callback you have two main options.

  1. Make the call blocking. This essentially means that you will have to force the function to stall until the OkHttp callback is triggered before return map; occurs. I would absolutely not recommend doing this as it defeats the entire purpose of moving long running tasks to other threads.

  2. Call a function inside the onResponse() callback. Construct the map inside the callback itself, then just call a function with that map as a parameter to handle any operations you need to do on that map . Alternatively you could also make the map a global variable so you can access it from practically anywhere.

On a sidenote, if this data is going to be used to propagate changes back to the UI or other program state, I would recommend using a ViewModel (it's a model object that holds data and can outlive Activity lifecycles) paired with something like MutableLiveData (which is a data wrapper that makes basically anything observable).

Using such a setup, you would have your map object inside your ViewModel. You would then register an observer from any context (Activity, Fragment, etc) where you need to know about updates on the map . Finally, in the callback, you would just need to update the ViewModel's map . This would automatically notify any registered observers.

Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM