1

I need to fetch some data from my server in order to make my app work. In order to do that, I will be using POST. As far as I know, I have to request that data in a thread which can not be the main thread. I am finding it a little bit difficult to put the data I am receiving in a variable defined in the UI thread. So, my question is, which is the best way to do it?

Is it correct to set the value of a variable defined, for example, in my main activity, with a setter called inside an AsyncTask? Or is there a better option than this?

Thread nt = new Thread(){
        @Override
    public void run(){

            try{

               //get data with POST and then something like main.setValue(data);
            }catch(Exception e){

                e.printStackTrace();
            }
        }

    };
    nt.start();

I have read that I may use Interfaces in order to archive that, but it is a concept that I do not understand very well yet. I would like to directly use a method which returns the data, but as far as I know, it is not possible.

EDIT: new code according to NoChinDeluxe answer:

public class LoginHandler {

public static class Login extends AsyncTask<String, String, Integer> {


    LoginCallback listener;

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


        URL url;

        postDataParams.put("name", params[0]);
        HashMap<String, String> postDataParams = new HashMap<String, String>();
        postDataParams.put("password", params[1]);

        try {

            url = new URL("http://mashiron.xyz/_03z/gmpia/proc.php");

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(15000);
            conn.setConnectTimeout(15000);
            conn.setRequestMethod("POST");
            conn.setDoInput(true);
            conn.setDoOutput(true);


            OutputStream os = conn.getOutputStream();
            BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(os, "UTF-8"));

            writer.write(HttpHandler.getPostDataString(postDataParams));
            writer.flush();
            writer.close();
            os.close();
            System.out.println("Respuesta: "+conn.getResponseCode());
            return conn.getResponseCode();

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

            return 404;
        }
    }

    protected void onPostExecute(int result){
        System.out.println("Respuesta 2: "+result);

        listener.onResultReceived(result);
    }

}



public interface LoginCallback {

    void onResultReceived(int result);
}

}

EDIT: added exception for NoChinDeluxe:

03-24 17:38:09.072 13312-13312/com.pitazzo.geomoments E/AndroidRuntime: FATAL EXCEPTION: main Process: com.pitazzo.geomoments, PID: 13312 java.lang.NullPointerException: Attempt to invoke interface method 'void com.pitazzo.geomoments.Handlers.LoginHandler$LoginCallback.onResultReceived(int)' on a null object reference at com.pitazzo.geomoments.Handlers.LoginHandler$Login.onPostExecute(LoginHandler.java:65) at com.pitazzo.geomoments.Handlers.LoginHandler$Login.onPostExecute(LoginHandler.java:17) at android.os.AsyncTask.finish(AsyncTask.java:636) at android.os.AsyncTask.access$500(AsyncTask.java:177) at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:653) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5300) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)

EDIT: more code for NoChainDeluxe

public class LoginActivity extends AppCompatActivity implements LoginHandler.LoginCallback{

EditText name;
EditText password;
Button login;
int code;


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

    /*
    if(logueado){

    }


     */


    name = (EditText) findViewById(R.id.loginuser);
    password = (EditText) findViewById(R.id.loginpassword);
    login = (Button) findViewById(R.id.loginlogin);

    login.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            String params[] = {name.getText().toString(), password.getText().toString()};
            System.out.println("Params: "+params.toString());

            new LoginHandler.Login().execute(params);
            System.out.println("Respuesta 4: "+code);

            if(code == 200){
                Toast toast1 =
                        Toast.makeText(getApplicationContext(),
                                "Iniciado sesión", Toast.LENGTH_SHORT);

                toast1.show();
            }else{

                Toast toast1 =
                        Toast.makeText(getApplicationContext(),
                                "Nombre de usuario y/o contraseña incorrectos: "+code, Toast.LENGTH_SHORT);

                toast1.show();

            }

        }
    });

}

public void onResultReceived(int resultado) {
    code = resultado;
    System.out.println("Respuesta 3: "+code);

}

}
cottontail
  • 10,268
  • 18
  • 50
  • 51
pitazzo
  • 1,125
  • 1
  • 13
  • 28
  • Use AysncTask fro your needs. You don't need any setter if you use it. In onPostExecute() method of AsyncTask , you can updated your UI. – Shadab Ansari Mar 22 '16 at 22:39

3 Answers3

5

The best way to achieve this is to use an HttpURLConnection to make your web calls inside an AsyncTask and then pass the result back to your calling Activity through a callback. Here's some code to help you get started:

The first thing you should understand is how to properly use a callback with an AsyncTask. Here is an example AsyncTask that defines a callback interface:

import android.os.AsyncTask;

public class TestTask extends AsyncTask<String, Void, String> {

    TestTaskCallback listener;

    public TestTask(TestTaskCallback listener) {
        this.listener = listener;
    }

    protected String doInBackground(String... args) {

        String input = args[0];
        String output = "simulated return value";

        return output;
    }

    protected void onPostExecute(String result) {
        listener.onResultReceived(result);
    }

    public interface TestTaskCallback {
        void onResultReceived(String result);
    }
}

The way this works is, you define a public interface that you then implement in your Activity. This acts as a "listener" that is waiting for any data that is sent through to it. We define the interface TestTaskCallback because we are going to be sending our data from our AsyncTask to our calling Activity.

Then in the Activity, we need to implement this interface, and pass in a reference to our implementation to the task when we create it. That way, when the task fires, it knows where to send the result, which is back to our Activity. An example implementation might look like this:

public class TestActivity extends AppCompatActivity implements TestTask.TestTaskCallback {

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

        new TestTask(this).execute("Some input");

    }

    public void onResultReceived(String result) {
        Log.d("TEST TASK RESULT", result);
    }
}

So our Activity implements the interface that we defined inside our AsyncTask, and notice that our AsyncTask takes the reference to this implementation (passed in through the constructor) and sends data to it in the onPostExecute() method. This will allow your result to be sent to the main UI thread so that you can update your Activity appropriately.

The only thing left is to actually make the web calls. I would recommend using an HttpURLConnection for this. You would put this code inside the doInBackground() method of your AsyncTask.

I'll show you an example web service call I have set up. This shows how to make a web service call to retrieve a JSON response. It looks something like this:

//The JSON we will get back as a response from the server
JSONObject jsonResponse = null;

//Http connections and data streams
URL url;
HttpURLConnection httpURLConnection = null;
OutputStreamWriter outputStreamWriter = null;

try {

    //open connection to the server
        url = new URL("your_url_to_web_service");
        httpURLConnection = (HttpURLConnection) url.openConnection();

        //set request properties
        httpURLConnection.setDoOutput(true); //defaults request method to POST
        httpURLConnection.setDoInput(true);  //allow input to this HttpURLConnection
        httpURLConnection.setRequestProperty("Content-Type", "application/json"); //header params
        httpURLConnection.setRequestProperty("Accept", "application/json"); //header params
        httpURLConnection.setFixedLengthStreamingMode(jsonToSend.toString().getBytes().length); //header param "content-length"

        //open output stream and POST our JSON data to server
        outputStreamWriter = new OutputStreamWriter(httpURLConnection.getOutputStream());
        outputStreamWriter.write(jsonToSend.toString());
        outputStreamWriter.flush(); //flush the stream when we're finished writing to make sure all bytes get to their destination

        //prepare input buffer and get the http response from server
        StringBuilder stringBuilder = new StringBuilder();
        int responseCode = httpURLConnection.getResponseCode();

        //Check to make sure we got a valid status response from the server,
        //then get the server JSON response if we did.
        if(responseCode == HttpURLConnection.HTTP_OK) {

            //read in each line of the response to the input buffer
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(),"utf-8"));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line).append("\n");
            }

            bufferedReader.close(); //close out the input stream

            try {
                //Copy the JSON response to a local JSONObject
                jsonResponse = new JSONObject(stringBuilder.toString());
            } catch (JSONException je) {
                je.printStackTrace();
            }
        }

} catch (IOException ioe) {
    ioe.printStackTrace();
} finally {
    if(httpURLConnection != null) {
        httpURLConnection.disconnect(); //close out our http connection
    }

    if(outputStreamWriter != null) {
        try {
            outputStreamWriter.close(); //close our output stream
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}

//Return the JSON response from the server.
return jsonResponse;

This is pretty much all you need to know to do exactly what it is you are trying to do. I realize this is a ton of info to throw at you all at once, but if you take your time and work through it piece by piece, you'll find it's not too difficult after all and is actually a VERY powerful tool that you'll use all the time programming Android apps!

Hope this helps. Feel free to ask questions for any parts you don't fully understand yet!

NoChinDeluxe
  • 3,446
  • 1
  • 16
  • 29
  • AWSOME! Thank you so much, I will try it and let you know any question Thanks once again! – pitazzo Mar 22 '16 at 23:36
  • Hey NoChinDeluxe! I'm experimenting a problem. I have defined my asyntask as you said, and the callback interface as well. But I noticed the listener was not working. With the help of some debugging messages I discovered the problem was that my onPostExecute() method was never called. Because of his length, I will post my code in a new edit of my question, hope you can find my mistake. – pitazzo Mar 23 '16 at 14:43
  • What about your print line inside the try block: `System.out.println("Respuesta: "+conn.getResponseCode());` <-- does this get called? If not, you may be forgetting to call `execute()` on your task (something I forget ALL the time). – NoChinDeluxe Mar 23 '16 at 18:10
  • Yep, it gets called and I didn't forget calling the execute() method. I put other debug message in the doInBackground() body and that one gets called always. I noticed that inside the AsynTask body, my onPostExecute() method was highlighted in a grey colour. But once I changed the argument of the method from int to Integer, it looked like the doInBackground() method. However, with this change my app crashes with a NullPointerException (I changed from int to Integer in all the other parts of the code where the "result" was used as well, but the app keeps crashing). Thanks :) – pitazzo Mar 24 '16 at 15:49
  • Here is a little capture of the highlighted thing: [link](http://i65.tinypic.com/2mht0lk.png) – pitazzo Mar 24 '16 at 15:52
  • Oh yeah, ok, I see what the issue is. You are returning an `Integer` from doInBackground(), but you are trying to pass an int to your callbacks. The way deal with this is to pass an `Integer` into onPostExecute(), which you have to do because that's what you're returning from doInBackground(). That should remove the gray highlighting...then for your callback do this instead: `listener.onResultReceived(result.intValue());` That will unbox your Integer object into a primitive int, which is what you're wanting to pass through the callback. – NoChinDeluxe Mar 24 '16 at 16:02
  • Thanks, with that change my onPostExecute() method gets called and the code inside runs, but now my app crashes. I'm getting the following exception: (see my original question) – pitazzo Mar 24 '16 at 16:41
  • Looks like your LoginCallback reference is null. I'd have to see the code of your Activity that implements the callback. Some likely causes are that you forgot to use the `implements` keyword in your Activity class definition or you implemented the method itself incorrectly. – NoChinDeluxe Mar 24 '16 at 21:08
  • I've just added the code of my Login Activity to the question. Thanks! – pitazzo Mar 24 '16 at 21:12
  • Your problem is, you never assign a reference to your Activity to the callback listener. Notice in my code, I pass in `this` to the AsyncTask constructor and assign it to my callback listener. That way, the task knows where to send the result. In your code, you haven't connected the two. – NoChinDeluxe Mar 24 '16 at 21:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/107286/discussion-between-pitazzo-and-nochindeluxe). – pitazzo Mar 24 '16 at 23:54
1

Better use an AsyncTask to propagate the data to your UI thread, just use onPostExecute() to set the result on your activity's class.

Shmulik Klein
  • 3,754
  • 19
  • 34
0

The error you are getting is because of accessing UI elements from background thread.

AsyncTask is a Thread pool based api that runs your task in a seperate thread ,but your UI part runs in a thread usually called UI thread, to update any changes to ui put the logic onPostExecute()

NOTE: Use okhttp to get consistent http api, it also supports http2.Their github wiki is very helpful, check here for examples.

Renjith Thankachan
  • 4,178
  • 1
  • 30
  • 47