How to wait for a helper thread to complete before continuing with the remainder of the UI thread

Hello everyone,

I’ve run into a problem that I’m not sure how to deal with as a novice Android developer. Whenever I call the GetValueThread to retrieve a value from an ultrasonic sensor from the Photon, the UI thread continues and attempts to update the TextView that I would like to contain the sensor value.

MainActivity.java:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;

public class MainActivity extends Activity {
    private Button mGetCurrentReadingButton;
    public TextView mCurrentReadingValue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ParticleDeviceSetupLibrary.init(this.getApplicationContext(), MainActivity.class);

        // Declare and assign our buttons and text
        mGetCurrentReadingButton = (Button) findViewById(R.id.getCurrentReadingButton);
        mCurrentReadingValue = (TextView) findViewById(R.id.currentReadingValue);

        final View.OnClickListener getResult = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                GetValueThread thread = new GetValueThread();
                thread.setName("GetValueThread");
                thread.start();
            try {
                thread.join();
                int result = thread.getResultCode();
                displayCurrentValue(result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    mGetCurrentReadingButton.setOnClickListener(getResult);
}
private void displayCurrentValue(int result){
    mCurrentReadingValue.setText(result + " cm");
    Log.d("mCurrentReadingValue", result + " cm");
    Log.d("Result", "Executing the previous line of code before a result has been captured from Photon.");
}
}

And the GetValueThread.java:

 import android.support.annotation.NonNull;
    import android.util.Log;
    
    import java.io.IOException;
    
    import io.particle.android.sdk.cloud.ParticleCloudException;
    import io.particle.android.sdk.cloud.ParticleCloudSDK;
    import io.particle.android.sdk.cloud.ParticleDevice;
    import io.particle.android.sdk.utils.Async;
    
    import static io.particle.android.sdk.utils.Py.list;
    
    public class GetValueThread extends Thread {
        public int resultCode;
    
        @Override
        public void run() {
            try {
                getValue();
                getResultCode();
            } catch (ParticleCloudException | ParticleDevice.VariableDoesNotExistException | IOException e) {
                e.printStackTrace();
            }
        }
    
        public void getValue() throws ParticleCloudException, IOException, ParticleDevice.VariableDoesNotExistException {
            try {
                final ParticleDevice myDevice = ParticleCloudSDK.getCloud().getDevice("trailblazer01");
    
                Async.executeAsync(myDevice, new Async.ApiWork<ParticleDevice, Integer>() {
    
                    public Integer callApi(@NonNull ParticleDevice particleDevice)
                            throws ParticleCloudException, IOException {
                        try {
                            resultCode = myDevice.callFunction("range1", list("Get range 1"));
                            Log.d("aString", "Result of calling range1: " + resultCode);
                            return resultCode;
                        } catch (ParticleDevice.FunctionDoesNotExistException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
    
                    @Override
                    public void onSuccess(@NonNull Integer s) {
                        Log.d("Result", resultCode + " cm");
                    }
    
                    @Override
                    public void onFailure(@NonNull ParticleCloudException e) {
                        Log.e("Result", "Something went wrong making an SDK call: ", e);
                    }
                });
    
            } catch (ParticleCloudException e) {
                e.printStackTrace();
            }
    
        }
    
        public int getResultCode() {
            return resultCode;
        }
    }

From above, when running the code, I get the debug message: “Executing the previous line of code before a result has been captured from Photon.” before I get the debug message from GetValueThread displaying the data retrieved.

How can I ensure that the TextView is only updated after the GetValueThread has had time to run. Also note, the time taken for the thread appears to vary slightly such that I’d prefer to avoid a static delay (e.g. 2000 ms) to wait for it to complete.

Thanks!

AFAIK you can use the promise pattern with the current Particle Android SDK.
This way you’d not need to run your own thread, but the system will take care of the callback.

Thanks for the Speedy response @ScruffR!

Did you mean a framework like this?

Yup, that’s the same idea.
I’ve only used these with ParticleJS but that should (hopefully) work the same way.

Big thanks to @ScruffR for his help pointing me in the direction of Promises. However, in the interest of making a quick prototype, I’ve removed the asynchronous part of the thread, and exclusively used blocking calls (way way slower, but the only way I got it to work for the time being).

My final Java code is below for anyone else who wanders by.

MainActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;

public class MainActivity extends Activity {
    public TextView mCurrentReadingValue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ParticleDeviceSetupLibrary.init(this.getApplicationContext(), MainActivity.class);

        // Declare and assign our buttons and text
        Button getCurrentReadingButton = (Button) findViewById(R.id.getCurrentReadingButton);
        mCurrentReadingValue = (TextView) findViewById(R.id.currentReadingValue);

        View.OnClickListener getResult = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                GetValueThread thread = new GetValueThread();
                thread.setName("GetValueThread");
                thread.start();
                try {
                    thread.join();
                    int result = thread.getResultCode();
                    displayCurrentValue(result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        getCurrentReadingButton.setOnClickListener(getResult);
    }

    private void displayCurrentValue(int result){
        mCurrentReadingValue.setText(result + " cm");
        Log.d("mCurrentReadingValue", result + " cm");
        Log.d("Result", "Executing the previous line of code before a result has been captured from Photon.");
    }
}

GetValueThread.java

import android.support.annotation.NonNull;
import android.util.Log;

import java.io.IOException;

import io.particle.android.sdk.cloud.ParticleCloudException;
import io.particle.android.sdk.cloud.ParticleCloudSDK;
import io.particle.android.sdk.cloud.ParticleDevice;
import io.particle.android.sdk.utils.Async;

import static io.particle.android.sdk.utils.Py.list;

public class GetValueThread extends Thread {
    public int resultCode;

    @Override
    public void run() {
        try {
//            getValue();
            final ParticleDevice myDevice = ParticleCloudSDK.getCloud().getDevice("trailblazer01");
            blockingCalls(myDevice);
            getResultCode();
        } catch (ParticleCloudException e) {
            e.printStackTrace();
        }
    }


    public void getValue() throws ParticleCloudException, IOException, ParticleDevice.VariableDoesNotExistException {
        try {
            final ParticleDevice myDevice = ParticleCloudSDK.getCloud().getDevice("trailblazer01");

            Async.executeAsync(myDevice, new Async.ApiWork<ParticleDevice, Integer>() {

                public Integer callApi(@NonNull ParticleDevice particleDevice)
                        throws ParticleCloudException, IOException {
                    try {
                        resultCode = myDevice.callFunction("range1", list("Get range 1"));
                        Log.d("aString", "Result of calling range1: " + resultCode);
                        return resultCode;
                    } catch (ParticleDevice.FunctionDoesNotExistException e) {
                        e.printStackTrace();
                    }
                    return null;
                }

                @Override
                public void onSuccess(@NonNull Integer s) {
                    Log.d("Result", resultCode + " cm");
                }

                @Override
                public void onFailure(@NonNull ParticleCloudException e) {
                    Log.e("Result", "Something went wrong making an SDK call: ", e);
                }
            });

        } catch (ParticleCloudException e) {
            e.printStackTrace();
        }

    }
    public int blockingCalls(ParticleDevice myDevice){
        try {
            resultCode = myDevice.callFunction("range1", list("Get range 1"));
            Log.d("aString", "Result of calling range1: " + resultCode);
        } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
            e.printStackTrace();
        }
        return resultCode;
    }

    public int getResultCode() {
        Log.d("Get result code", "Result of calling range1: " + resultCode);
        return resultCode;
    }
}

If I were to attempt this again, I would certainly look into promises, but I was looking to moving onto the SoftAP side of the Photon instead of learning enough JS to understand promises. Other alternatives that you could try are SwingWorkers and Executors fo managing asynchronous events.

1 Like