Check out the Android sample app. It's a Contacts app that lists all Liferay users and shows contact details about them. It contains many examples on how to use the Liferay Mobile SDK and it's a good way to learn how to use the Liferay Mobile SDK to build your own native applications.
-
Download the latest version of
liferay-android-sdk.jar
. -
Within your Android project, copy the JAR into your project's
/libs
folder. Android Developer Tools should automatically add this JAR to your classpath. -
You will also need to download and copy these dependencies to the
/libs
folder: httpclient-android-4.3.3.jar and httpmime-4.3.3.jar. -
Start using it!
If your Android project is using Gradle as the build system, you can add Liferay Android SDK as a dependency to your project, all versions are available at JCenter and Maven Central repositories:
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile group: 'com.liferay.mobile', name: 'liferay-android-sdk', version: '6.2.0.+'
}
You can do the same and add to your pom.xml if you are using Maven:
<dependency>
<groupId>com.liferay.mobile</groupId>
<artifactId>liferay-android-sdk</artifactId>
<version>LATEST</version>
</dependency>
Gradle and Maven will download all the necessary dependencies before building your project.
If you get errors such as
Duplicate files copied in APK META-INF/NOTICE
when building with Gradle, add this to yourbuild.gradle
file:android { ... packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' } ... }
Each Liferay Mobile SDK is designed to work with a specific Liferay Portal version. Because of that, its version scheme reflects the compatible Liferay version.
For example, Liferay Mobile SDK 6.2.0.1 is built to work with Liferay Portal 6.2.0, while Liferay Mobile SDK 7.0.0.1 works with Liferay Portal 7.0.0.
The fourth integer in the version (6.2.0.x) is related to internal Liferay Mobile SDK versions. For example, if a bug is found on 6.2.0.1, we will release a version called 6.2.0.2 with the bug fix.
This doesn't mean you can't support several Liferay versions in the same app though. You can add to your classpath both versions 6.2.0.1 and 7.0.0.1. There won't be conflicts because service classes packages are separated by their version number as well: *.v62, *.v7, etc.
To find out which Liferay versions you are connecting to, use the
PortalVersionUtil.getPortalVersion(…)
method.
The Liferay Android SDK is compatible with Android versions 2.3.3 and up. It may work with older versions, but these are the versions we use to run our unit tests.
-
Create a
Session
with the user credentials:import com.liferay.mobile.android.service.Session; import com.liferay.mobile.android.service.SessionImpl; Session session = new SessionImpl("http://10.0.2.2:8080", "[email protected]", "test");
The first parameter is the URL of the Liferay instance you are connecting to. If you are running your app on an Android Emulator, it points to your local Liferay instance. In this case,
http://10.0.2.2:8080
is equivalent tohttp://localhost:8080
, which means the emulator and Liferay are running in the same machine.The second parameter can be the user's email address, screen name, or user ID. It depends on which authentication method your Liferay instance is using. The default authentication method requires the user's email address.
The third parameter is the user's password.
Be careful to use these credentials on a production Liferay instance. If you're using the administrator credentials, you have permission to call any service and can change any data by accident.
-
Check which Liferay services you need in order to build your app by navigating to http://localhost:8080/api/jsonws. This page lists all available portal services and plugin services.
-
If you are building a blogs app, for example, you can import
BlogsEntryService
.import com.liferay.mobile.android.v62.blogsentry.BlogsEntryService;
Since the SDK is built for a specific Liferay version, service classes are separated by their package name. In this case, it's
.v62
, which means this SDK is built for Liferay 6.2. You can use several SDKs at the same time to support different Liferay versions. -
Create a
BlogsEntryService
object and make a service call.BlogsEntryService service = new BlogsEntryService(session); JSONArray jsonArray = service.getGroupEntries(10184, 0, 0, -1, -1);
It fetches all blog entries from the
Guest
site, which, in this example, hasgroupId
equal to 10184.This is a basic example of a synchronous service call; the method will only return after the request is finished.
Service method return types can be
void
,String
,JSONArray
, andJSONObject
. Primitive type wrappers can beBoolean
,Integer
,Long
, andDouble
.Many service methods require
groupId
as a parameter. You can get the user's groups by calling thegetUserSites()
method fromGroupService
.
Android doesn't allow synchronous HTTP requests, like we demonstrated above,
from the main UI thread. You can only make them from different threads; for
example, from within an AsyncTask
instance.
The SDK can help you make asynchronous HTTP requests if you don't want to create
an AsyncTask
yourself. It makes the service call from an AsyncTask and you can
pass a callback that is invoked after the request finishes.
Set a callback class implementation to the session and all the following service calls will be asynchronous. Set it back to null if you want to make synchronous calls again.
import com.liferay.mobile.android.task.callback.AsyncTaskCallback;
import com.liferay.mobile.android.task.callback.typed.JSONArrayAsyncTaskCallback;
AsyncTaskCallback callback = new JSONArrayAsyncTaskCallback() {
public void onFailure(Exception exception) {
// Implement exception handling code
}
public void onSuccess(JSONArray result) {
// Called after request has finished successfully
}
};
session.setCallback(callback);
service.getGroupEntries(10184, 0, 0, -1, -1);
If an exception occurs during the request, the onFailure
method is called. It
can be either a connection exception (e.g., a request timeout) or a
ServerException
.
When a ServerException
occurs, it's because something went wrong on the
server side. For example, if you pass a groupId
that doesn't exist, the portal
will complain about it and the SDK will wrap the error message with a
ServerException
.
There are many AsyncTaskCallback
implementations, one for each service method
return type: JSONObjectAsyncTaskCallback
, JSONArrayAsyncTaskCallback
,
StringAsyncTaskCallback
, BooleanAsyncTaskCallback
,
IntegerAsyncTaskCallback
, LongAsyncTaskCallback
, and
DoubleAsyncTaskCallback
. Pick the appropriate implementation for your service
method return type. In the example above, since getGroupEntries
returns a
JSONArray, you must use the JSONArrayAsyncTaskCallback
instance.
It's also possible to use a generic AsyncTaskCallback
implementation called
GenericAsyncTaskCallback
. For this implementation, you must implement a
transform method and handle JSON parsing by yourself.
If you still don't want to use any of these callbacks, you can implement
AsyncTaskCallback
directly, but be careful, you should always get the first element of the JSONArray passed as a parameter to theonPostExecute(JSONArray jsonArray)
method (i.e.,jsonArray.get(0)
).
The onSuccess
method is called on the main UI thread after the request has
finished.
Since the request is asynchronous, service.getGroupEntries
returns
immediately with a null object. The result will be passed to the callback
onSuccess
method instead.
The SDK allows sending requests using batch processing, which can be much more efficient in some cases. For example, you want to delete 10 blog entries at the same time; instead of making one request for each delete call, you can create a batch of calls and send them all together.
import com.liferay.mobile.android.service.BatchSessionImpl;
BatchSessionImpl batch = new BatchSessionImpl(session);
BlogsEntryService service = new BlogsEntryService(batch);
service.deleteEntry(1);
service.deleteEntry(2);
service.deleteEntry(3);
JSONArray jsonArray = batch.invoke();
First, create a BatchSessionImpl
session. You can either pass credentials or
another session
to the constructor. This is useful when you already have a
session
object and want to reuse the same credentials.
Then, make the service calls as usual; as with asynchronous calls, these methods return a null object right away.
Finally, call the invoke()
method from the session object. It returns a
JSONArray
containing the results for each service call. Since there are three
deleteEntry
calls, the jsonArray
contains three objects. The order of the
results matches the order of the service calls.
If you want to make batch calls asynchronously, set the callback as a
BatchAsyncTaskCallback
instance:
import com.liferay.mobile.android.task.callback.BatchAsyncTaskCallback;
batch.setCallback(new BatchAsyncTaskCallback() {
public void onFailure(Exception exception) {
}
public void onSuccess(JSONArray results) {
// The result is always a JSONArray
}
});
As you can see, the return type is always a JSONArray
.
There are some special cases in which service methods arguments are not
primitives. In these cases, you should use JSONObjectWrapper
, for example:
JSONObjectWrapper wrapper = new JSONObjectWrapper(new JSONObject());
You must pass a JSON containing the object properties and their values. On the server side, your object will be instantiated and setters for each property will called with the values from the JSON you passed.
There are some other cases in which server service methods require interfaces or
abstract classes arguments. Since it's impossible for the SDK to guess which
implementation you want to use, you must initialize JSONObjectWrapper
with
the className, like that:
JSONObjectWrapper wrapper = new JSONObjectWrapper(className, new JSONObject());
The server will look for the class name in its classpath and instantiate the
object for you, then call setters just like the previous example.
OrderByComparator
is a good example, more about that bellow.
On the server side, OrderByComparator
is an abstract class, because of that,
you must pass the name of a class that implements it, for example:
String className = "com.liferay.portlet.bookmarks.util.comparator.EntryNameComparator";
JSONObjectWrapper orderByComparator = new JSONObjectWrapper(className, new JSONObject());
If the service you are calling accepts null
for a comparator argument, just
pass null
to the service call.
You probably want to set the ascending property for a comparator. Unfortunately,
as of Liferay 6.2, most Liferay OrderByComparator
implementations don't have a
setter for this property and it's not possible to set from the SDK. This will be
fixed in future portal versions.
However, if you have a custom OrderByComparator
that has a setter for
ascending you can do:
String className = "com.example.MyOrderByComparator";
JSONObject jsonObject = new JSONObject();
jsonObject.put("ascending", true);
JSONObjectWrapper orderByComparator = new JSONObjectWrapper(className, jsonObject);
For more examples, take a look at this test case: OrderByComparatorTest.java.
ServiceContext
is a special case because most Liferay services methods require
it, however, you are not required to pass it to the SDK, you can just pass
null
. The server will create a ServiceContext
instance with default values
for you.
If there is some property you want to set for ServiceContext
you can do:
JSONObject jsonObject = new JSONObject();
jsonObject.put("addGroupPermissions", true);
jsonObject.put("addGuestPermissions", true);
JSONObjectWrapper serviceContext = new JSONObjectWrapper(jsonObject);
For more examples, take a look at this test case: ServiceContextTest.java.
Some Liferay services require argument types such as byte arrays (byte[]
) and
Files (java.io.File
).
The SDK converts byte arrays to Strings before sending the POST request, for
example, "hello".getBytes("UTF-8")
becomes a JSON array like
"[104,101,108,108,111]"
. The SDK does that for you so you don't have worry
about it, you just need to pass the byte array to the method.
You need to be careful while using such methods though, because you are allocating memory for the whole byte array and may have memory issues if content is large.
Some other portal service methods require java.io.File
, in these cases the
SDK requires InputStreamBody
instead.
Here's an example on how to create InputStreamBody
instances:
InputStream is = context.getAssets().open("file.png");
InputStreamBody file = new InputStreamBody(is, "image/png", "file.png");
That is, you need to create an InputStream
and pass it to the
InputStreamBody
constructor along with the file's mime type and name.
The SDK will send a multipart form request to the portal. On the server side, a
File
instance will be created and sent to the service method you are calling.
In case you want to listen for upload progress to create a progress bar, you can
create a UploadProgressAsyncTaskCallback
callback and set to the current
Session
object, its onProgress
method will be called for each byte chunk
sent, it will pass the total number of uploaded bytes so far. For example:
session.setCallback(
new UploadProgressAsyncTaskCallback<JSONObject>() {
(...)
public void onProgress(int totalBytes) {
// This method will be called for each byte chunk sent.
// The totalBytes argument will contain the total number
// of uploaded bytes, from 0 to the total size of the
// request.
}
(...)
}
);
For more examples on this subject, check these test cases: DLAppServiceTest.java (aadFileEntry* methods) and UploadFileUtil.java.