Restful API service
Asked Answered
L

11

231

I'm looking to make a service which I can use to make calls to a web-based REST API.

Basically I want to start a service on app init then I want to be able to ask that service to request a url and return the results. In the meantime I want to be able to display a progress window or something similar.

I've created a service currently which uses IDL, I've read somewhere that you only really need this for cross app communication, so think these needs stripping out but unsure how to do callbacks without it. Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)

Currently I have a service with post and get http methods inside, a couple of AIDL files (for two way communication), a ServiceManager which deals with starting, stopping, binding etc to the service and I'm dynamically creating a Handler with specific code for the callbacks as needed.

I don't want anyone to give me a complete code base to work on, but some pointers would be greatly appreciated.

Code in (mostly) full:

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
    return binder;
}
public void onCreate() {
    super.onCreate();
}
public void onDestroy() {
    super.onDestroy();
    mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
    public void doLogin(String username, String password) {

        Message msg = new Message();
        Bundle data = new Bundle();
        HashMap<String, String> values = new HashMap<String, String>();
        values.put("username", username);
        values.put("password", password);
        String result = post(Config.getURL("login"), values);
        data.putString("response", result);
        msg.setData(data);
        msg.what = Config.ACTION_LOGIN;
        mHandler.sendMessage(msg);
    }

    public void registerCallback(IRemoteServiceCallback cb) {
        if (cb != null)
            mCallbacks.register(cb);
    }
};

private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        // Broadcast to all clients the new value.
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                switch (msg.what) {
                case Config.ACTION_LOGIN:
                    mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
                    break;
                default:
                    super.handleMessage(msg);
                    return;

                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }
    public String post(String url, HashMap<String, String> namePairs) {...}
    public String get(String url) {...}
};

A couple of AIDL files:

package com.something.android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

and

package com.something.android
import com.something.android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

and the service manager:

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    public IRestfulService restfulService;
    private RestfulServiceConnection conn;
    private boolean started = false;
    private Context context;

    public ServiceManager(Context context) {
        this.context = context;
    }

    public void startService() {
        if (started) {
            Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.startService(i);
            started = true;
        }
    }

    public void stopService() {
        if (!started) {
            Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.stopService(i);
            started = false;
        }
    }

    public void bindService() {
        if (conn == null) {
            conn = new RestfulServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.bindService(i, conn, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

    protected void destroy() {
        releaseService();
    }

    private void releaseService() {
        if (conn != null) {
            context.unbindService(conn);
            conn = null;
            Log.d(LOG_TAG, "unbindService()");
        } else {
            Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

    class RestfulServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className, IBinder boundService) {
            restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
            try {
            restfulService.registerCallback(mCallback);
            } catch (RemoteException e) {}
        }

        public void onServiceDisconnected(ComponentName className) {
            restfulService = null;
        }
    };

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        public void userLogIn(String result) throws RemoteException {
            mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));

        }
    };

    private Handler mHandler;

    public void setHandler(Handler handler) {
        mHandler = handler;
    }
}

Service init and bind:

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

service function call:

// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();

application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);

try {
    servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
    e.printStackTrace();
}

...later in the same file...

Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        switch (msg.what) {
        case Config.ACTION_LOGIN:

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            try {
                ...process login results...
                }
            } catch (JSONException e) {
                Log.e("JSON", "There was an error parsing the JSON", e);
            }
            break;
        default:
            super.handleMessage(msg);
        }

    }

};
Lammergeier answered 7/7, 2010 at 17:34 Comment(2)
This might be very helpful for people learning Android REST client implementation. Dobjanschi's presentation transcribed into a PDF: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…Grotto
Since many people recommended Virgil Dobjanschi's presentation and the link to IO 2010 is broken now, here's the direct link to the YT video: youtube.com/watch?v=xHXn3Kg2IQEDuhon
D
286

If your service is going to be part of you application then you are making it way more complex than it needs to be. Since you have a simple use case of getting some data from a RESTful Web Service, you should look into ResultReceiver and IntentService.

This Service + ResultReceiver pattern works by starting or binding to the service with startService() when you want to do some action. You can specify the operation to perform and pass in your ResultReceiver (the activity) through the extras in the Intent.

In the service you implement onHandleIntent to do the operation that is specified in the Intent. When the operation is completed you use the passed in ResultReceiver to send a message back to the Activity at which point onReceiveResult will be called.

So for example, you want to pull some data from your Web Service.

  1. You create the intent and call startService.
  2. The operation in the service starts and it sends the activity a message saying it started
  3. The activity processes the message and shows a progress.
  4. The service finishes the operation and sends some data back to your activity.
  5. Your activity processes the data and puts in in a list view
  6. The service sends you a message saying that it is done, and it kills itself.
  7. The activity gets the finish message and hides the progress dialog.

I know you mentioned you didn't want a code base but the open source Google I/O 2010 app uses a service in this way I am describing.

Updated to add sample code:

The activity.

public class HomeActivity extends Activity implements MyResultReceiver.Receiver {

    public MyResultReceiver mReceiver;

    public void onCreate(Bundle savedInstanceState) {
        mReceiver = new MyResultReceiver(new Handler());
        mReceiver.setReceiver(this);
        ...
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("command", "query");
        startService(intent);
    }

    public void onPause() {
        mReceiver.setReceiver(null); // clear receiver so no leaks.
    }

    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case RUNNING:
            //show progress
            break;
        case FINISHED:
            List results = resultData.getParcelableList("results");
            // do something interesting
            // hide progress
            break;
        case ERROR:
            // handle the error;
            break;
    }
}

The Service:

public class QueryService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        String command = intent.getStringExtra("command");
        Bundle b = new Bundle();
        if(command.equals("query") {
            receiver.send(STATUS_RUNNING, Bundle.EMPTY);
            try {
                // get some data or something           
                b.putParcelableArrayList("results", results);
                receiver.send(STATUS_FINISHED, b)
            } catch(Exception e) {
                b.putString(Intent.EXTRA_TEXT, e.toString());
                receiver.send(STATUS_ERROR, b);
            }    
        }
    }
}

ResultReceiver extension - edited about to implement MyResultReceiver.Receiver

public class MyResultReceiver implements ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}
Duodiode answered 7/7, 2010 at 17:53 Comment(29)
Good answer, although the pattern is a little hard to completely wrap my head around. Would you mind adding some example code? That would be really great!Sokol
Excellent - thank you! I ended up going through the Google iosched application and wow...it's pretty complex but I have something which is working, now I just need to work out why it works! But yes, this is the basic pattern that I have working. thank you very much.Lammergeier
Can not find any ResultReceiver interface which my activity could implement. Instead it is a concrete class which has to be extended and used. Nevertheless good and helpful post!Java
@K. Claszen You are correct I was doing this from memory and in the sdk api it calls ResultReceiver an interface when it is actually a class. I added my ResultReceiver extension to make the example more complete and correct. I actually use this pattern in a few of my successful apps. Let me know if you have any other questions.Duodiode
But in case the user closes the app during service call the UI won't be refreshed on resuming. How to solve that?Craddock
@Craddock Then the question is if the user goes to another app do they care about the results? If the answer is yes then the service should save the results somewhere (db) that the app would load when the user restarted it.Duodiode
@Robby Pond The user might go to another app during the loading process. During this I show a ProgressDialog, which won't disappear because the activity isn't notified about new results.Craddock
Excellent post +1, just thinking what is the different if used Broadcast Receiver, sample link"mobile.tutsplus.com/tutorials/android/…"Monitor
@Robby Pond Thank you for this answer this is how complex it should be for simple apps and not that Google IO session overkill. However I've run into a little mess with this. It's working perfectly well but I want to provide a user with a chance to cancel request. So you click on a button, service is started, progress wheel is spinning and you press back key. What happens in the code then? Is it possible to kill an intent service and how? How do you deal with this kind of situation?Inquisition
Is there any way to use this on a service that has already been started? i.e. App A has a service that is started before App B is launched. App B is then launched and then wants to make a call and get a result from the service running in App A.Routine
One small addition to the answer: as you do the mReceiver.setReceiver(null); in the onPause method, you should do the mReceiver.setReceiver(this); in the onResume method. Else you might not receive the events if your activity is resumed without being re-createdEnnead
I'm trying to wrap my head around all these different parts... does anyone have a link to this same content/pattern/idea, just a bit more verbose?Chatwin
Don't the docs say that you don't have to call stopSelf, since IntentService does that for you?Demp
@MikaelOhlson Correct, you should not call stopSelf if you subclass IntentService because if you do, you'll lose any pending requests to the same IntentService.Kaitlinkaitlyn
what about when the receiver is null? would need to send a notification to the activity or something... arg!Gravy
Thanks for the great answer. Can the resultant logic be done directly in the MyResultReceiver since the handler is also available? Or is it simply good design to delegate control back to the activity?Basilio
What if the Activity is destroyed? What happens when invoking receiver.send? If the handler has no queue to post to, it'll throw a RuntimeException. I guess I'm missing something.Haugen
If I need to show the progress of download process in a notification bar lets say, and update it every 1 second so I can see its current progress. Then, is it OK to use the ResultReceiver to send the progress to notification every 1 second? Or should I use in this case a BroadcastReceiver, or it's the same?Merit
even this simplified structure is pretty 'heady' - but I've got it up and running - thanks for your efforts on thisAlpinist
so the up-to-date pattern to do this is AsyncTask?Bourassa
It's a very good pattern, you can find a library that's implements this pattern (and more generally the Service Pattern in Google's IO video) : RESTDroidAvatar
@RobbyPond Hi i sort of understand you code and would like to implement in lieu of AsyncTask. I have put MyResultReceiver as a public inner class of my activity but where does the variable of type Receive come from? Thanks in advanceDrugstore
RoboSpice is actually the easiest way to implement a REST client inside a service on Android. (I can tell, I am one of the contributors ;))Proliferous
@Aerovistae Yes this still works. Another approach now used are Non-UI fragments that can live beyond the current Activity.Duodiode
You can do the same Via Messengers, but the whole command pattern is too much when you are just calling a local service.Dinahdinan
IntentService will kill itself when task is complete, so this.stopSelf()is unnecessary.Archon
Maybe I'm missing something here, but I am dumbfounded why we are going through all this effort to make an API call? What's wrong with a simple AsyncTask? Why do we need to launch a service to do this?Funderburk
@MartinKonecny if the request is one that MUST complete, AsyncTask won't be sufficient - if the app is killed (e.g. low memory and user takes a call) the request won't necessarily complete. If the request is run from a service, the process is less likely to be killed when the application moves to the background. Service processes have higher priority than background processes. For pulling data for UI display it doesn't matter if request is incomplete, but if you're posting an update from the app to a server then it would be since the data might be lost when the app is killed.Leven
@RobbyPond what is the significance of having a Receiver inside a MtResultReceiver ? Can't activity just implement ResultReceiver ? Can u pls explainSmaze
A
18

Developing Android REST client applications has been an awesome resource for me. The speaker does not show any code, he just goes over design considerations and techniques in putting together a rock solid Rest Api in android. If your a podcast kinda person or not, I'd recommend giving this one at least one listen but, personally I've listened to it like 4 or five times thus far and I'm probably going to listen to it again.

Developing Android REST client applications
Author: Virgil Dobjanschi
Description:

This session will present architectural considerations for developing RESTful applications on the Android platform. It focuses on design patterns, platform integration and performance issues specific to the Android platform.

And there are so many considerations I really hadn't made in the first version of my api that I've had to refactor

Acima answered 20/9, 2011 at 12:35 Comment(5)
+1 This contains the kinds of considerations you would never think of when you start out.Whitelaw
Yeah my first foray into developing a Rest client was almost exactly his description of what exactly not to do (Thankfully I realized much of this was wrong before I watched this). I lold at that a bit.Acima
I've seen this video more than once and I'm implementing the second pattern. My problem is that I need to use transactions in my complex database model to update local data from fresh data that comes from the server, and the ContentProvider interface doesn't provide me a way to do it. Do you have any suggestion, Terrance?Mope
NB: Comments by Dobjanschi on HttpClient no longer hold. See https://mcmap.net/q/20485/-urlconnection-or-httpclient-which-offers-better-functionality-and-more-efficiencyTerramycin
Yes, HttpURLConnection is now preferred. Further, with the release of Android 6.0, support for the Apache HTTP Client has officially been removed.Micahmicawber
R
16

Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)

No you have to create a thread yourself, a Local service runs in the UI thread by default.

Rigobertorigor answered 7/7, 2010 at 18:34 Comment(0)
S
11

I know @Martyn does not want full code, but I think this annotation its good for this question:

10 Open Source Android Apps which every Android developer must look into

Foursquared for Android is open-source, and have an interesting code pattern interacting with the foursquare REST API.

Staggard answered 12/9, 2011 at 19:38 Comment(0)
L
6

I would highly recommend the REST client Retrofit.

I have found this well written blog post extremely helpful, it also contains simple example code. The author uses Retrofit to make the network calls and Otto to implement a data bus pattern:

http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html

Loose answered 27/9, 2014 at 10:4 Comment(0)
S
5

Just wanted to point you all in the direction of an standalone class I rolled that incorporates all of the functionality.

http://github.com/StlTenny/RestService

It executes the request as non-blocking, and returns the results in an easy to implement handler. Even comes with an example implementation.

Stunt answered 11/7, 2012 at 18:1 Comment(0)
F
4

Lets say I want to start the service on an event - onItemClicked() of a button. The Receiver mechanism would not work in that case because :-
a) I passed the Receiver to the service (as in Intent extra) from onItemClicked()
b) Activity moves to the background. In onPause() I set the receiver reference within the ResultReceiver to null to avoid leaking the Activity.
c) Activity gets destroyed.
d) Activity gets created again. However at this point the Service will not be able to make a callback to the Activity as that receiver reference is lost.
The mechanism of a limited broadcast or a PendingIntent seems to be more usefull in such scenarios- refer to Notify activity from service

Flavia answered 17/3, 2011 at 9:49 Comment(2)
there is a problem with what you are saying. that is that when the activity moves to background it isn't destroyed... so the receiver still exists and the activity context also.Progeny
@Progeny When an Activity is paused or stopped it can be killed by the Android system in low memory situations. Refer to Activity Lifecycle.Duero
F
4

Note that the solution from Robby Pond is somehow lacking: in this way you only allow todo one api call at a time since the IntentService only handles one intent at a time. Often you want to perform parallel api calls. If you want todo this you have to extend Service instead of IntentService and create your own thread.

Floozy answered 28/4, 2011 at 8:15 Comment(1)
You can still do multiple calls on the IntentService by delegating the webservice api calls to an executor-thread-service, present as a member variable of your IntentService-derived classDeterminate
F
2

Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)

In this case its better to use asynctask, which runs on a different thread and return result back to the ui thread on completion.

Floro answered 4/11, 2011 at 3:49 Comment(0)
E
1

There is another approach here which basically helps you to forget about the whole management of the requests. It is based on an async queue method and a callable/callback based response. The main advantage is that by using this method you'll be able to make the whole process (request, get and parse response, sabe to db) completely transparent for you. Once you get the response code the work is already done. After that you just need to make a call to your db and you are done. It helps as well with the problematic of what happens when your activity is not active. What will happen here is that you'll have all your data saved in your local database but the response won't be processed by your activity, that's the ideal way.

Estipulate answered 5/7, 2012 at 14:59 Comment(2)
Dead link. The domain has expired.Duero
Link does not exist anymore.Slapdash
M
1

Robby provides a great answer, though I can see you still looking for more information. I implemented REST api calls the easy BUT wrong way. It wasn't until watching this Google I/O video that I understood where I went wrong. It's not as simple as putting together an AsyncTask with a HttpUrlConnection get/put call.

Medici answered 8/4, 2013 at 18:6 Comment(1)
Dead link. Here is the updated one - Google I/O 2010 - Android REST client applicationsCarbazole

© 2022 - 2024 — McMap. All rights reserved.