Android - Thread within an activity allowed to call member functions of a bound service?
<p>I have a service bound to an activity. The service will access a JSON web service. I would like to create a thread within the activity that calls a member function of the bound service but I am receiving an error. </p>

<p>The following example functions perfectly if I don't use a thread. </p>

<p>Here is the activity:</p>

<pre><code>import java.util.ArrayList;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;


public class ProjectsListActivity extends ListActivity {

    ApiService mService;
    boolean mBound = false;

    private static String TAG = "tag";
    private ProgressDialog mProgressDialog = null;
    private ArrayList&lt;Project&gt; mProjects = null;
    private ProjectAdapter mAdapter;
    private Runnable viewProjects;

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

        // set the layout
        setContentView(R.layout.project_list_item);

        // setup the custom list adapter
        mProjects = new ArrayList&lt;Project&gt;();
        this.mAdapter = new ProjectAdapter(this, R.layout.row, mProjects);
        setListAdapter(this.mAdapter);

        ListView lv = getListView();
        lv.setTextFilterEnabled(true);

        lv.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView&lt;?&gt; parent, View view,
                    int position, long id) {

                if (mBound) {
                    Project p = mProjects.get(position);
                    Toast.makeText(getApplicationContext(), p.getName(),
                            Toast.LENGTH_SHORT).show();
                }
            }
        });

        // use a thread to get the projects
        viewProjects = new Runnable() {
            @Override
            public void run() {
                getProjects();
            }
        };
        Thread thread = new Thread(null, viewProjects, "MagentoBackground");
        thread.start();
        mProgressDialog = ProgressDialog.show(ProjectsListActivity.this,
                "Please wait...", "Retrieving data ...", true);

    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, ApiService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = ((ApiService.LocalBinder)service).getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };

    private class ProjectAdapter extends ArrayAdapter&lt;Project&gt; {

        private ArrayList&lt;Project&gt; items;

        public ProjectAdapter(Context context, int textViewResourceId,
                ArrayList&lt;Project&gt; items) {
            super(context, textViewResourceId, items);
            this.items = items;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater vi = (LayoutInflater) getContext()
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.row, parent, false);
            }
            Project o = items.get(position);
            if (o != null) {
                TextView tt = (TextView) v.findViewById(R.id.toptext);
                TextView bt = (TextView) v.findViewById(R.id.bottomtext);
                if (tt != null) {
                    tt.setText("Name: " + o.getName());
                }
                if (bt != null) {
                    bt.setText("Status: " + o.getId());
                }
            }
            return v;
        }
    }

    private Runnable returnRes = new Runnable() {
        @Override
        public void run() {
            if (mProjects != null &amp;&amp; mProjects.size() &gt; 0) {
                mAdapter.notifyDataSetChanged();
                for (int i = 0; i &lt; mProjects.size(); i++)
                    mAdapter.add(mProjects.get(i));
            }
            mProgressDialog.dismiss();
            mAdapter.notifyDataSetChanged();
        }
    };

    private void getProjects() {
        try {
            mProjects = mService.getUserProjects();
            Log.i(TAG, "" + mProjects.size());
        } catch (Exception e) {
            Log.e("BACKGROUND_PROC", "Exception ["+e.getMessage()+"]", e);
        }
        runOnUiThread(returnRes);
    }

}
</code></pre>

<p>Here is the service:</p>

<pre><code>import java.util.ArrayList;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;


public class ApiService extends Service {

    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();

    /**
     * Class used for the client Binder. Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        ApiService getService() {
            return ApiService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public ArrayList&lt;Project&gt; getUserProjects() {

        ArrayList&lt;Project&gt; mProjects = new ArrayList&lt;Project&gt;();
        Project p1 = new Project();
        p1.setName("Project 1");
        p1.setId("Pending");
        Project p2 = new Project();
        p2.setName("Project 2");
        p2.setId("Completed");
        mProjects.add(p1);
        mProjects.add(p2);

        return mProjects;
    }
}
</code></pre>

<p>The error I receive:</p>

<pre><code>06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285): Exception [null]
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285): java.lang.NullPointerException
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at ProjectsListActivity.getProjects(ProjectsListActivity.java:160)
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at ProjectsListActivity.access$3(ProjectsListActivity.java:158)
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at ProjectsListActivity$4.run(ProjectsListActivity.java:67)
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at java.lang.Thread.run(Thread.java:1096)
</code></pre>

<p>If this still doesn't make sense, essentially what I am trying to do is take the <a href="http://developer.android.com/guide/topics/fundamentals/bound-services.html" rel="nofollow" title="bound service">bound service</a> example with the number generator and modify it so that the method is called within a thread in the activity. Within the example it even says, "However, if this call were something that might hang, then this request should occur in a separate thread to avoid slowing down the activity performance." </p>

<p>Thanks in advance for your help. </p>
