In Liferay Screens, a Connector is a class that interacts asynchronously with
local and remote data sources and Liferay instances. Recall that callbacks also
make asynchronous service calls. So why bother with a Connector? Connectors
provide a layer of abstraction by making your service call outside your
Interactor. For example,
the Interactor in the Screenlet creation tutorial
makes the server call and and processes its results via LRCallback
. This
Screenlet could instead make its server call in a separate Connector class,
leaving the Interactor to instantiate the Connector and receive its results.
Connectors also let you validate your Screenlet’s data. For more information on
Connectors, see the
tutorial on the architecture of Liferay Screens for iOS.
This tutorial walks you through the steps required to create and use a Connector with your Screenlets, using the advanced version of the sample Add Bookmark Screenlet as an example. This Screenlet contains two actions:
-
Add Bookmark: Adds a bookmark to the Bookmarks portlet in a @product@ installation. This tutorial shows you how to create and use a Connector for this action.
-
Get Title: Retrieves the title from a bookmark URL entered by the user. This tutorial shows you how to use a pre-existing Connector with this action.
Before proceeding, make sure you’ve read the Screenlet creation tutorial. First, you’ll learn how to create your Connector.
Creating Connectors
When you create your Connector class, be sure to follow the naming convention specified in the best practices tutorial.
Use the following steps to implement your Connector class:
-
Create your Connector class by extending the
ServerConnector
class. For example, here’s the class declaration for Add Bookmark Screenlet’s Connector class,AddBookmarkLiferayConnector
:public class AddBookmarkLiferayConnector: ServerConnector { ... }
-
Add the properties needed to call the Mobile SDK service, then create an initializer that sets those properties. For example,
AddBookmarkLiferayConnector
needs properties for the bookmark’s folder ID, title, and URL. It also needs an initializer to set those properties:public let folderId: Int64 public let title: String public let url: String public init(folderId: Int64, title: String, url: String) { self.folderId = folderId self.title = title self.url = url super.init() }
-
If you want to validate any of your Screenlet’s properties, override the
validateData
method to implement validation for those properties. You can use theValidationError
class to encapsulate the errors. For example, the followingvalidateData
implementation inAddBookmarkLiferayConnector
ensures thatfolderId
is greater than0
, andtitle
andurl
contain values. This method also usesValidationError
to represent the error:override public func validateData() -> ValidationError? { let error = super.validateData() if error == nil { if folderId <= 0 { return ValidationError("Undefined folderId") } if title.isEmpty { return ValidationError("Title cannot be empty") } if url.isEmpty { return ValidationError("URL cannot be empty") } } return error }
-
Override the
doRun
method to call the Mobile SDK service you need to call. This method should retrieve the result from the service and store it in a public property. Also be sure to handle errors and empty results. For example, the following code defines theresultBookmarkInfo
property for storing the service’s results retrieved in thedoRun
method. Note that this method’s service call is identical to the one in theAddBookmarkInteractor
class’sstart
method in the Screenlet creation tutorial. ThedoRun
method, however, takes the additional step of saving the result to theresultBookmarkInfo
property. Also note that thisdoRun
method handles errors asNSError
objects:public var resultBookmarkInfo: [String:AnyObject]? override public func doRun(session session: LRSession) { let service = LRBookmarksEntryService_v7(session: session) do { let result = try service.addEntryWithGroupId(LiferayServerContext.groupId, folderId: folderId, name: title, url: url, description: "Added from Liferay Screens", serviceContext: nil) if let result = result as? [String: AnyObject] { resultBookmarkInfo = result lastError = nil } else { lastError = NSError.errorWithCause(.InvalidServerResponse) resultBookmarkInfo = nil } } catch let error as NSError { lastError = error resultBookmarkInfo = nil } }
Well done! Now you know how to create a Connector class. To see the finished
example AddBookmarkLiferayConnector
class,
click here.
Using Connectors
To use a Connector, your Interactor class must extend the
ServerConnectorInteractor
class
or one of its following subclasses:
-
ServerReadConnectorInteractor
: Your Interactor class should extend this class when implementing an action that retrieves information from a server or data source. -
ServerWriteConnectorInteractor
: Your Interactor class should extend this class when implementing an action that writes information to a server or data source.
When extending ServerConnectorInteractor
or one of its subclasses, your
Interactor class only needs to override the createConnector
and
completedConnector
methods. These methods create a Connector instance and
recover the Connector’s result, respectively.
Follow these steps to use a Connector in your Interactor:
-
Set your Interactor class’s superclass to
ServerConnectorInteractor
or one of its subclasses. You should also remove any code that conforms a callback protocol, if it exists. For example, Add Bookmark Screenlet’s Interactor class (AddBookmarkInteractor
) extendsServerWriteConnectorInteractor
because it writes data to a @product@ installation. At this point, your Interactor should contain only the properties and initializer that it requires:public class AddBookmarkInteractor: ServerWriteConnectorInteractor { public let folderId: Int64 public let title: String public let url: String public var resultBookmark: Bookmark? //MARK: Initializer public init(screenlet: BaseScreenlet, folderId: Int64, title: String, url: String) { self.folderId = folderId self.title = title self.url = url super.init(screenlet: screenlet) } }
-
Override the
createConnector
method to return an instance of your Connector. For example, thecreateConnector
method inAddBookmarkInteractor
returns anAddBookmarkLiferayConnector
instance created with thefolderId
,title
, andurl
properties:public override func createConnector() -> ServerConnector? { return AddBookmarkLiferayConnector(folderId: folderId, title: title, url: url) }
-
Override the
completedConnector
method to get the result from the Connector and store it in the appropriate property. For example, thecompletedConnector
method inAddBookmarkInteractor
first casts itsServerConnector
argument toAddBookmarkLiferayConnector
. It then gets the Connector’sresultBookmarkInfo
property and sets it to the Interactor’sresultBookmark
property:override public func completedConnector(c: ServerConnector) { if let addCon = (c as? AddBookmarkLiferayConnector), bookmarkInfo = addCon.resultBookmarkInfo { self.resultBookmark = bookmarkInfo } }
That’s it! To see the complete example AddBookmarkInteractor
,
click here.
If your Screenlet uses multiple Interactors, follow the same steps to use
Connectors. Also, Screens provides
the ready-to-use HttpConnector
for interacting with non-Liferay URL’s. To use this Connector, set your
Interactor to use HttpConnector
. For example, the Add Bookmark Screenlet
action that retrieves a URL’s title doesn’t interact with a @product@
installation; it retrieves the title directly from the URL. Because this
action’s Interactor class (GetWebTitleInteractor
) retrieves data, it extends
ServerReadConnectorInteractor
. It also overrides the createConnector
and
completedConnector
methods to use HttpConnector
. Here’s the complete
GetWebTitleInteractor
:
import UIKit
import LiferayScreens
public class GetWebTitleInteractor: ServerReadConnectorInteractor {
public let url: String?
// title from the webpage
public var resultTitle: String?
//MARK: Initializer
public init(screenlet: BaseScreenlet, url: String) {
self.url = url
super.init(screenlet: screenlet)
}
//MARK: ServerConnectorInteractor
public override func createConnector() -> ServerConnector? {
if let url = url, URL = NSURL(string: url) {
return HttpConnector(url: URL)
}
return nil
}
override public func completedConnector(c: ServerConnector) {
if let httpCon = (c as? HttpConnector), data = httpCon.resultData,
html = NSString(data: data, encoding: NSUTF8StringEncoding) {
self.resultTitle = parseTitle(html)
}
}
//MARK: Private methods
// Parse the title from the webpage's HTML
private func parseTitle(html: NSString) -> String {
let range1 = html.rangeOfString("<title>")
let range2 = html.rangeOfString("</title>")
let start = range1.location + range1.length
return html.substringWithRange(NSMakeRange(start, range2.location - start))
}
}
Awesome! Now you know how to create and use Connectors in your Screenlets.