76

I'm getting familiar with Android framework and Java and wanted to create a general "NetworkHelper" class which would handle most of the networking code enabling me to just call web-pages from it.

I followed this article from the developer.android.com to create my networking class: http://developer.android.com/training/basics/network-ops/connecting.html

Code:

package com.example.androidapp;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.util.Log;



/**
 * @author tuomas
 * This class provides basic helper functions and features for network communication.
 */


public class NetworkHelper 
{
private Context mContext;


public NetworkHelper(Context mContext)
{
    //get context
    this.mContext = mContext;
}


/**
 * Checks if the network connection is available.
 */
public boolean checkConnection()
{
    //checks if the network connection exists and works as should be
    ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();

    if (networkInfo != null && networkInfo.isConnected())
    {
        //network connection works
        Log.v("log", "Network connection works");
        return true;
    }
    else
    {
        //network connection won't work
        Log.v("log", "Network connection won't work");
        return false;
    }

}

public void downloadUrl(String stringUrl)
{
    new DownloadWebpageTask().execute(stringUrl);

}



//actual code to handle download
private class DownloadWebpageTask extends AsyncTask<String, Void, String>
{



    @Override
    protected String doInBackground(String... urls)
    {
        // params comes from the execute() call: params[0] is the url.
        try {
            return downloadUrl(urls[0]);
        } catch (IOException e) {
            return "Unable to retrieve web page. URL may be invalid.";
        }
    }

    // Given a URL, establishes an HttpUrlConnection and retrieves
    // the web page content as a InputStream, which it returns as
    // a string.
    private String downloadUrl(String myurl) throws IOException 
    {
        InputStream is = null;
        // Only display the first 500 characters of the retrieved
        // web page content.
        int len = 500;

        try {
            URL url = new URL(myurl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(10000 );
            conn.setConnectTimeout(15000);
            conn.setRequestMethod("GET");
            conn.setDoInput(true);
            // Starts the query
            conn.connect();
            int response = conn.getResponseCode();
            Log.d("log", "The response is: " + response);
            is = conn.getInputStream();

            // Convert the InputStream into a string
            String contentAsString = readIt(is, len);
            return contentAsString;

        // Makes sure that the InputStream is closed after the app is
        // finished using it.
        } finally {
            if (is != null) {
                is.close();
            } 
        }
    }

    // Reads an InputStream and converts it to a String.
    public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException 
    {
        Reader reader = null;
        reader = new InputStreamReader(stream, "UTF-8");        
        char[] buffer = new char[len];
        reader.read(buffer);
        return new String(buffer);
    }


    // onPostExecute displays the results of the AsyncTask.
    @Override
    protected void onPostExecute(String result) 
    {
        //textView.setText(result);
        Log.v("log", result);

    }

} 

}

In my activity class I use the class this way:

connHelper = new NetworkHelper(this);

...

if (connHelper.checkConnection())
    {
        //connection ok, download the webpage from provided url
        connHelper.downloadUrl(stringUrl);
    }

Problem I'm having is that I should somehow make a callback back to the activity and it should be definable in "downloadUrl()" function. For example when download finishes, public void "handleWebpage(String data)" function in activity is called with loaded string as its parameter.

I did some googling and found that I should somehow use interfaces to achieve this functionality. After reviewing few similar stackoverflow questions/answers I didn't get it working and I'm not sure if I understood interfaces properly: How do I pass method as a parameter in Java? To be honest using the anonymous classes is new for me and I'm not really sure where or how I should apply the example code snippets in the mentioned thread.

So my question is how I could pass the callback function to my network class and call it after download finishes? Where the interface declaration goes, implements keyword and so on? Please note that I'm beginner with Java (have other programming background though) so I'd appreciate a throughout explanation :) Thank you!

Community
  • 1
  • 1
Tumetsu
  • 1,661
  • 2
  • 17
  • 29

7 Answers7

119

Use a callback interface or an abstract class with abstract callback methods.

Callback interface example:

public class SampleActivity extends Activity {

    //define callback interface
    interface MyCallbackInterface {

        void onDownloadFinished(String result);
    }

    //your method slightly modified to take callback into account 
    public void downloadUrl(String stringUrl, MyCallbackInterface callback) {
        new DownloadWebpageTask(callback).execute(stringUrl);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //example to modified downloadUrl method
        downloadUrl("http://google.com", new MyCallbackInterface() {

            @Override
            public void onDownloadFinished(String result) {
                // Do something when download finished
            }
        });
    }

    //your async task class
    private class DownloadWebpageTask extends AsyncTask<String, Void, String> {

        final MyCallbackInterface callback;

        DownloadWebpageTask(MyCallbackInterface callback) {
            this.callback = callback;
        }

        @Override
        protected void onPostExecute(String result) {
            callback.onDownloadFinished(result);
        }

        //except for this leave your code for this class untouched...
    }
}

The second option is even more concise. You do not even have to define an abstract method for "onDownloaded event" as onPostExecute does exactly what is needed. Simply extend your DownloadWebpageTask with an anonymous inline class inside your downloadUrl method.

    //your method slightly modified to take callback into account 
    public void downloadUrl(String stringUrl, final MyCallbackInterface callback) {
        new DownloadWebpageTask() {

            @Override
            protected void onPostExecute(String result) {
                super.onPostExecute(result);
                callback.onDownloadFinished(result);
            }
        }.execute(stringUrl);
    }

    //...
Tomasz Gawel
  • 8,379
  • 4
  • 36
  • 61
46

NO interface, NO lib, NO Java 8 needed!

Just using Callable<V> from java.util.concurrent

public static void superMethod(String simpleParam, Callable<Void> methodParam) {

    //your logic code [...]

    //call methodParam
    try {
        methodParam.call();

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

How to use it:

 superMethod("Hello world", new Callable<Void>() {
                public Void call() {
                    myParamMethod();
                    return null;
                }
            }
    );

Where myParamMethod() is our passed method as parameter (in this case methodParam).

Vasile Doe
  • 1,674
  • 1
  • 24
  • 40
  • Thanks for the answer. However the example doesn't make it clear (i've been up all night, so forgive my woolheaded question) how the myParamMethod is passed the simpleParam. For example, I have a wrapper around Ion to which I pass the server params and the target URL encased in Json, do I go superMethod(serverParams, callEndpointIon); or do I have to override the Callable every single time? – kilokahn Jul 23 '18 at 08:43
  • 1
    If you're using `Callable` there is no real reason not to use a `Runnable` instead, since you're returning Void anyway. It would remove the need for that `return null;` statement. – Nathan F. Aug 08 '19 at 06:04
  • 2
    ... and no input parameters )) – Nolesh Aug 17 '19 at 13:16
  • 1
    how i cant pass paremeter to callable as methodParam.call(object); and receive public Void call(JSONObject object) { // myParamMethod(JSONObject object); return null; } – Jhon Jesus Jul 25 '20 at 14:55
  • This is perfect! – Darwin Marcelo Jan 26 '22 at 02:16
28

Yes, an interface is the best way IMHO. For example, GWT uses the command pattern with an interface like this:

public interface Command{
    void execute();
}

In this way, you can pass function from a method to another

public void foo(Command cmd){
  ...
  cmd.execute();
}

public void bar(){
  foo(new Command(){
     void execute(){
        //do something
     }
  });
}
Arnaud Denoyelle
  • 29,980
  • 16
  • 92
  • 148
  • 6
    Whats GWT and how to pass any parameter? – Buksy Sep 20 '13 at 09:40
  • 1
    @Buksy is It what you looking for ? public interface Command{ void execute(Object... object); } To pass unlimited objects :D – M at Oct 10 '17 at 19:00
  • You can use `java.lang.Runnable` for this, which is basically the same approach, but you don't have to define an interface – BamsBamx Mar 07 '23 at 09:29
11

The out of the box solution is that this is not possible in Java. Java does not accept Higher-order functions. It can be achieved though by some "tricks". Normally the interface is the one used as you saw. Please take a look here for further information. You can also use reflection to achieve it, but this is error prone.

olorin
  • 182
  • 4
7

Using Interfaces may be the best way in Java Coding Architecture.

But, passing a Runnable object could work as well, and it would be much more practical and flexible, I think.

 SomeProcess sp;

 public void initSomeProcess(Runnable callbackProcessOnFailed) {
     final Runnable runOnFailed = callbackProcessOnFailed; 
     sp = new SomeProcess();
     sp.settingSomeVars = someVars;
     sp.setProcessListener = new SomeProcessListener() {
          public void OnDone() {
             Log.d(TAG,"done");
          }
          public void OnFailed(){
             Log.d(TAG,"failed");
             //call callback if it is set
             if (runOnFailed!=null) {
               Handler h = new Handler();
               h.post(runOnFailed);
             }
          }               
     };
}

/****/

initSomeProcess(new Runnable() {
   @Override
   public void run() {
       /* callback routines here */
   }
});
Infinity
  • 91
  • 1
  • 3
0

Reflection is never a good idea since it's harder to read and debug, but if you are 100% sure what you're doing, you can simply call something like set_method(R.id.button_profile_edit, "toggle_edit") to attach a method to a view. This is useful in fragment, but again, some people would consider it as anti-pattern so be warned.

public void set_method(int id, final String a_method)
{
    set_listener(id, new View.OnClickListener() {
        public void onClick(View v) {
            try {
                Method method = fragment.getClass().getMethod(a_method, null);
                method.invoke(fragment, null);
            } catch (Exception e) {
                Debug.log_exception(e, "METHOD");
            }
        }
    });
}
public void set_listener(int id, View.OnClickListener listener)
{
    if (root == null) {
        Debug.log("WARNING fragment", "root is null - listener not set");
        return;
    }
    View view = root.findViewById(id);
    view.setOnClickListener(listener);
}
superarts.org
  • 7,009
  • 1
  • 58
  • 44
0

Interface callback support generic type.

In Callbackable.java

public interface Callbackable<T> {
    void call(T obj);
}

How to use:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        doSomeNetworkAction(new Callbackable<Integer>() {
            @Override
            public void call(Integer obj) {
                System.out.println("have received: " + obj + " from network");
            }
        });

    }
    
    // You can change Integer to String or to any model class like Customer, Profile, Address... 
    public void doSomeNetworkAction(Callbackable<Integer> callback) {
        // acb xyz...
        callback.call(666);
    }
}
Huynh Inc
  • 2,010
  • 1
  • 25
  • 42