Check out the iOS 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-ios-sdk.zip
. -
Unzip the file into your Xcode project.
-
Within Xcode, right click on your project and click on Add Files to 'Project Name'.
-
Add both
core
andv62
folders. Thev62
folder name can change for each Liferay version. In this example, the SDK is built for Liferay 6.2. -
The iOS SDK requires AFNetworking 2.2.4. Make sure its source code is added to your project.
-
You need CocoaPods installed.
-
Create a file called
Podfile
in your project and add the following line:pod 'Liferay-iOS-SDK'
-
Run
$ pod install
. -
This will download the latest version of the SDK and create a .xcworkspace file, use that file to open your project in Xcode.
For more information on how CocoaPods works, read their documentation.
CocoaPods will download all the necessary dependencies and configure your
workspace when you run $ pod install
.
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 project both versions 6.2.0.1 and 7.0.0.1. There won't be conflicts because service classes names are suffixed with their version number as well: *_v62.m, *_v7.m, etc.
To find out which Liferay versions you are connecting to, use the
[LRPortalVersionUtil getPortalVersion:…]
method.
The Liferay iOS SDK is compatible with iOS versions 6.1 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 "LRSession.h" LRSession *session = [[LRSession alloc] init:@"http://localhost:8080" username:@"[email protected]" password:@"test"];
The first parameter is the URL of the Liferay instance you are connecting to. In this case, 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 "LRBlogsEntryService_v62.h"
Since the SDK is built for a specific Liferay version, service class names have the Liferay version they are built for. 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 an
LRBlogsEntryService_v62
object and make a service call.LRBlogsEntryService_v62 *service = [[LRBlogsEntryService_v62 alloc] initWithSession:session]; NSError *error; NSArray *entries = [service getGroupEntriesWithGroupId:1084 status:0 start:-1 end:-1 error:&error];
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
,NSString
,NSArray
,NSDictionary
,NSNumber
, andBOOL
.Many service methods require
groupId
as a parameter. You can get the user's groups by calling[LRGroupService_v62 getUserSites:&error]
.
The SDK allows asynchronous HTTP requests; all you need to do is set a callback
to the session object. Set it back to nil
if you want to make synchronous
requests again.
The first thing you need to do is create a class that conforms to the
LRCallback
protocol. For example:
#import "LRCallback.h"
@interface BlogsEntriesCallback : NSObject <LRCallback>
@end
#import "BlogsEntriesCallback.h"
@implementation BlogsEntriesCallback
- (void)onFailure:(NSError *)error {
// Implement error handling code
}
- (void)onSuccess:(id)result {
// Called after request has finished successfully
}
@end
Then, set this callback to the session and call your service as usual:
BlogsEntriesCallback *callback = [[BlogsEntriesCallback alloc] init];
[session setCallback:callback];
[service getGroupEntriesWithGroupId:1084 status:0 start:-1 end:-1 error:&error];
If a server side exception or a connection error occurs during the request, the
onFailure
method will be called with an NSError
instance that contains
information about the error.
Since the request is asynchronous, getGroupEntriesWithGroupId
returns
immediately with nil, and the onSuccess
method of your callback is invoked
instead with the results once the request has finished successfully.
As you can see, the onSuccess
result parameter is not typed. You need to check
the service method signature in order to figure out which type you can cast to
safely. In this example, the getGroupEntriesWithGroupId
method returns a
NSArray
, so you can cast to this type:
- (void)onSuccess:(id)result {
NSArray *entries = (NSArray *)result;
}
onSuccess
is called on the main UI thread after the request has finished.
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 "LRBatchSession.h"
LRBatchSession *batch = [[LRBatchSession alloc] init:@"http://localhost:8080" username:@"[email protected]" password:@"test"];
LRBlogsEntryService_v62 *service = [[LRBlogsEntryService_v62 alloc] initWithSession:batch];
NSError *error;
[service deleteEntryWithEntryId:1 error:&error];
[service deleteEntryWithEntryId:2 error:&error];
[service deleteEntryWithEntryId:3 error:&error];
NSArray *entries = [batch invoke:&error];
First, create an LRBatchSession
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 will return nil right away.
Finally, call [batch invoke:&error]
; it will return an NSArray
containing the
results for each service call. Since there are three deleteEntryWithEntryId
calls, the entries array will contain 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 to the session as usual:
[batch setCallback:callback];
The return type for batch calls is always an NSArray
.
There are some special cases in which service methods arguments are not
primitives. In these cases, you should use LRJSONObjectWrapper
, for example:
LRJSONObjectWrapper *wrapper = [[LRJSONObjectWrapper alloc] initWithJSONObject:[NSDictionary dictionary]];
You must pass a dictionary 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 dictionary.
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 LRJSONObjectWrapper
with
the className, like that:
LRJSONObjectWrapper *wrapper = [[LRJSONObjectWrapper alloc] initWithClassName:@"com.example.MyClass" jsonObject:[NSDictionary dictionary]];
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:
NSString *className = @"com.liferay.portlet.bookmarks.util.comparator.EntryNameComparator";
LRJSONObjectWrapper *orderByComparator = [[LRJSONObjectWrapper alloc] initWithClassName:className jsonObject:[NSDictionary dictionary]];
If the service you are calling accepts null
for a comparator argument, just
pass nil
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:
NSString *className = @"com.example.MyOrderByComparator";
NSDictionary *jsonObject = @{
@"ascending": @(YES)
};
LRJSONObjectWrapper *orderByComparator = [[LRJSONObjectWrapper alloc] initWithClassName:className jsonObject:jsonObject];
For more examples, take a look at this test case: OrderByComparator.m.
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
nil
. 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:
NSDictionary *jsonObject = @{
@"addGroupPermissions": @(YES),
@"addGuestPermissions": @(YES)
};
LRJSONObjectWrapper *serviceContext = [[LRJSONObjectWrapper alloc] initWithJSONObject:jsonObject];
For more examples, take a look at this test case: ServiceContextTest.m.
Some Liferay services require binary argument types such as NSData
and
LRUploadData
.
The SDK converts NSData
instances to NSStrings before sending the POST
request, for example, [@"hello" dataUsingEncoding:NSUTF8StringEncoding]
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 NSData
to the
method.
You need to be careful while using such methods though, because you are
allocating memory for the whole NSData
and may have memory issues if content
is large.
Some other portal service methods require java.io.File
, in these cases the SDK
requires LRUploadData
instead.
Here are examples on how to create LRUploadData
instances:
LRUploadData *upload = [[LRUploadData alloc] initWithData:data fileName:@"file.png" mimeType:@"image/png"]
LRUploadData *upload = [[LRUploadData alloc] initWithInputStream:is length:length fileName:@"file.png" mimeType:@"image/png"];
The first constructor accepts a NSData
argument, while the second accepts
NSInputStream
.
As you can see, you also need to pass the file's mime type and name. lenght
is
the size in bytes of the content being sent.
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 LRProgressDelegate
delegate and set to LRUploadData
object, its
onProgressBytes
method will be called for each byte chunk sent, it will pass
the bytes that were sent, the total number of bytes sent for far and the total
size of the request. For example:
@interface ProgressDelegate : NSObject <LRProgressDelegate>
@end
@implementation ProgressDelegate
- (void)onProgressBytes:(NSUInteger)bytes sent:(long long)sent
total:(long long)total {
// bytes contains the byte values that were sent.
// sent will contain the total number of bytes sent.
// total will contain the total size of the request in bytes.
}
@end
For more examples on this subject, check this test case: FileUploadTest.m.