Creating iOS Screenlets

The built-in Screenlets cover common use cases for mobile apps that use Liferay. They authenticate users, interact with Dynamic Data Lists, display assets, and more. What if, however, there’s no Screenlet for your use case? No problem! You can create your own. Extensibility is a key strength of Liferay Screens.

This tutorial explains how to create your own Screenlets. As an example, it references code from the sample Add Bookmark Screenlet, that saves bookmarks to Liferay’s Bookmarks portlet.

In general, you use the following steps to create Screenlets:

  1. Plan Your Screenlet: Your Screenlet’s features and use cases determine where you’ll create it and the Liferay remote services you’ll call.

  2. Create Your Screenlet’s UI (its Theme): Although this tutorial presents all the information you need to create a Theme for your Screenlet, you may first want to learn the steps for creating a Theme. For more information on Themes in general, see the tutorial on using Themes with Screenlets.

  3. Create the Screenlet’s Interactor. Interactors are Screenlet components that make server calls.

  4. Create the Screenlet class. The Screenlet class is the Screenlet’s central component. It controls the Screenlet’s behavior and is the component the app developer interacts with when inserting a Screenlet.

Before getting started, make sure that you’re familiar with the architecture of Liferay Screens. Click here to read the Screens architecture tutorial.

Without further ado, let the Screenlet creation begin!

Planning Your Screenlet

Before creating your Screenlet, you must determine what it needs to do and how you want developers to use it. This determines where you’ll create your Screenlet and its functionality.

Where you should create your Screenlet depends on how you plan to use it. If you want to reuse or redistribute it, you should create it in an empty Cocoa Touch Framework project in Xcode. You can then use CocoaPods to publish it. The tutorial Packaging iOS Themes explains how to publish an iOS Screenlet. Even though that tutorial refers to Themes, the steps for preparing Screenlets for publication are the same. If you don’t plan to reuse or redistribute your Screenlet, create it in your app’s Xcode project.

You must also determine your Screenlet’s functionality and what data your Screenlet requires. This determines the actions your Screenlet must support and the Liferay remote services it must call. For example, Add Bookmark Screenlet only needs to respond to one action: adding a bookmark to Liferay’s Bookmarks portlet. To add a bookmark, this Screenlet must call the Liferay instance’s add-entry service for BookmarksEntry. If you’re running a Liferay instance locally on port 8080, click here to see this service. To add a bookmark, this service requires the following parameters:

  • groupId: The site ID in the Liferay instance that contains the Bookmarks portlet.

  • folderId: The folder ID in the Bookmarks portlet that receives the new bookmark.

  • name: The new bookmark’s title.

  • url: The new bookmark’s URL.

  • description: The new bookmark’s description.

  • serviceContext: A Liferay ServiceContext object.

Add Bookmark Screenlet must therefore account for each of these parameters. When saving a bookmark, the Screenlet asks the user to enter the bookmark’s URL and name. The user isn’t required, however, to enter any other parameters. This is because the app developer sets the groupId and folderId via the app’s code. Also, the Screenlet’s code automatically populates the description and serviceContext.

Great! Now you’re ready to create your Screenlet’s Theme!

Creating the Screenlet’s UI

In Liferay Screens for iOS, a Screenlet’s UI is called a Theme. Every Screenlet must have at least one Theme. A Theme has the following components:

  1. An XIB file: defines the UI components that the Theme presents to the end user.

  2. A View class: renders the UI, handles user interactions, and communicates with the Screenlet class.

First, create a new XIB file and use Interface Builder to construct your Screenlet’s UI. In many cases, the Screenlet’s actions must be triggered from the Theme. To achieve this, make sure to use a restorationIdentifier property to assign a unique ID to each UI component that triggers an action. The user triggers the action by interacting with the UI component. If the action only changes the UI’s state (that is, changes the UI component’s properties), then you can associate that component’s event to an IBAction method as usual. Actions using restorationIdentifier are intended for use by actions that need an Interactor, such as actions that make server requests or retrieve data from a database.

For example, Add Bookmark Screenlet’s UI needs text boxes for entering a bookmark’s URL and title. This UI also needs a button to support the Screenlet’s action: sending the bookmark to a Liferay instance. The XIB file AddBookmarkView_default.xib defines this UI. Because the button triggers the Screenlet’s action, it contains restorationIdentifier="add-bookmark".

Figure 1: Heres the sample Add Bookmark Screenlets XIB file rendered in Interface Builder.

Figure 1: Here's the sample Add Bookmark Screenlet's XIB file rendered in Interface Builder.

Now you must create your Screenlet’s View class. This class controls the UI you just defined. In the BaseScreenletView class, Screens provides the default functionality required by all View classes. Your View class must therefore extend BaseScreenletView to provide the functionality unique to your Screenlet. To support your UI, use standard @IBOutlets and @IBActions to connect all your XIB’s UI components and events to your View class. You should also implement getters and setters to get values from and set values to the UI components. Your View class should also implement any required animations or front-end logic.

For example, AddBookmarkView_default is Add Bookmark Screenlet’s View class. This class extends BaseScreenletView and contains @IBOutlet references to the XIB’s text fields. The getters for these references let the Theme retrieve the data the user enters into the corresponding text field:

import UIKit
import LiferayScreens

class AddBookmarkView_default: BaseScreenletView {

   @IBOutlet weak var URLTextField: UITextField?
   @IBOutlet weak var titleTextField: UITextField?

   var URL: String? {
       return URLTextField?.text
   }

   var title: String? {
       return titleTextField?.text
   }
}

In Interface Builder, you must now specify your View class as your XIB file’s custom class. In Add Bookmark Screenlet, for example, AddBookmarkView_default is set as the AddBookmarkView_default.xib file’s custom class in Interface Builder.

If you’re using CocoaPods, make sure to explicitly set a valid module for the custom class–the grayed-out Current default value only suggests a module.

Figure 2: In this XIB file, the custom classs module is NOT specified.

Figure 2: In this XIB file, the custom class's module is NOT specified.

Figure 3: The XIB file is bound to the custom class name, with the specified module.

Figure 3: The XIB file is bound to the custom class name, with the specified module.

Next, you’ll create your Screenlet’s Interactor.

Creating the Interactor

Create an Interactor class for each of your Screenlet’s actions. In the Interactor class, Screens provides the default functionality required by all Interactor classes. Your Interactor class must therefore extend Interactor to provide the functionality unique to your Screenlet.

Interactors work synchronously, but you can use callbacks (delegates) or Connectors to run their operations in the background. For example, the Liferay Mobile SDK provides the LRCallback protocol for this purpose. This is described in the Mobile SDK tutorial on invoking Liferay services asynchronously. Screens bridges this protocol to make it available in Swift. Your Interactor class can conform this protocol to make its server calls asynchronously. To implement an Interactor class:

  • Your initializer must receive all required properties and a reference to the Screenlet.
  • Override Interactor’s start method to perform the server operations your Screenlet requires (e.g., invoke a Liferay operation via a Liferay Mobile SDK service).
  • Save the server response to an accessible property, if necessary. For example, if the server call returns objects from a Liferay instance, you should store these objects in an accessible property. This way your Screenlet can display those results to the user.
  • You must invoke the methods callOnSuccess and callOnFailure to execute the closures onSuccess and onFailure, respectively.

For example, the sample Add Bookmark Screenlet’s Interactor class AddBookmarkInteractor makes the server call that adds a bookmark to a Liferay instance. This class extends the Interactor class and conforms the LRCallback protocol. The latter ensures that the Interactor’s server call runs asynchronously:

public class AddBookmarkInteractor: Interactor, LRCallback {...

To save the server call’s results, AddBookmarkInteractor defines the public variable resultBookmarkInfo. This class also defines public constants for the bookmark’s folder ID, title, and URL. The initializer sets these variables and calls Interactor’s constructor with a reference to the base Screenlet class (BaseScreenlet):

public var resultBookmarkInfo: [String:AnyObject]?
public let folderId: Int64
public let title: String
public let url: String

public init(screenlet: BaseScreenlet, folderId: Int64, title: String, url: String) {
    self.folderId = folderId
    self.title = title
    self.url = url
    super.init(screenlet: screenlet)
}

The AddBookmarkInteractor class’s start method makes the server call. To do so, it must first get a Session. Since Login Screenlet creates a session automatically upon successful login, the start method retrieves this session with SessionContext.createSessionFromCurrentSession(). To make the server call asynchronously, the start method must set a callback to this session. Because AddBookmarkInteractor conforms the LRCallback protocol, setting self as the session’s callback accomplishes this. The start method must then create a LRBookmarksEntryService_v7 instance and call this instance’s addEntryWithGroupId method. The latter method calls a Liferay instance’s add-entry service for BookmarksEntry. The start method therefore provides the groupId, folderId, name, url, description, and serviceContext arguments to addEntryWithGroupId. Note that this example provides a hard-coded string for the description. Also, the serviceContext is nil because the Mobile SDK handles the ServiceContext object for you:

override public func start() -> Bool {
    let session = SessionContext.createSessionFromCurrentSession()
    session?.callback = self

    let service = LRBookmarksEntryService_v7(session: session)

    do {
        try service.addEntryWithGroupId(LiferayServerContext.groupId,
                        folderId: folderId,
                        name: title,
                        url: url,
                        description: "Added from Liferay Screens",
                        serviceContext: nil)

        return true
    }
    catch {
        return false
    }
}

Finally, the AddBookmarkInteractor class must conform the LRCallback protocol by implementing the onFailure and onSuccess methods. The onFailure method communicates the NSError object that results from a failed server call. It does this by calling the base Interactor class’s callOnFailure method with the error. When the server call succeeds, the onSuccess method sets the server call’s results (the result argument) to the resultBookmarkInfo variable. The onSuccess method finishes by calling the base Interactor class’s callOnSuccess method to communicate the success status throughout the Screenlet:

public func onFailure(error: NSError!) {
    self.callOnFailure(error)
}

public func onSuccess(result: AnyObject!) {
    //Save result bookmark info
    resultBookmarkInfo = (result as! [String:AnyObject])

    self.callOnSuccess()
}

Next, you’ll create the Screenlet class.

Creating the Screenlet Class

The Screenlet class is the central hub of a Screenlet. It contains the Screenlet’s properties, a reference to the Screenlet’s View class, methods for invoking Interactor operations, and more. When using a Screenlet, app developers primarily interact with its Screenlet class. In other words, if a Screenlet were to become self-aware, it would happen in its Screenlet class (though we’re reasonably confident this won’t happen).

Screens’s BaseScreenlet class is a base Screenlet class implementation. Since BaseScreenlet provides most of a Screenlet class’s required functionality, your Screenlet class should extend BaseScreenlet. This lets you focus on your Screenlet’s unique functionality. Your Screenlet class must also include any @IBInspectable properties your Screenlet requires and a reference to your Screenlet’s View class. To perform your Screenlet’s action, your Screenlet class must override BaseScreenlet’s createInteractor method. This method should create an instance of your Interactor and then set the Interactor’s onSuccess and onFailure closures to define what happens when the server call succeeds or fails, respectively.

For example, the AddBookmarkScreenlet class is the Screenlet class in Add Bookmark Screenlet. This class extends BaseScreenlet and contains an @IBInspectable variable for the bookmark folder’s ID (folderId). The AddBookmarkScreenlet class’s createInteractor method first gets a reference to the View class (AddBookmarkView_default). It then creates an AddBookmarkInteractor instance with this Screenlet class (self), the folderId, the bookmark’s title, and the bookmark’s URL. Note that the View class reference contains the bookmark title and URL that the user entered into the UI. The createInteractor method then sets the Interactor’s onSuccess closure to print a success message when the server call succeeds. Likewise, the Interactor’s onFailure closure is set to print an error message when the server call fails. Note that you’re not restricted to only printing messages here: you should set these closures to do whatever is best for your Screenlet. The createInteractor method finishes by returning the Interactor instance. Here’s the complete AddBookmarkScreenlet class:

import UIKit
import LiferayScreens


public class AddBookmarkScreenlet: BaseScreenlet {

    //MARK: Inspectables

    @IBInspectable var folderId: Int64 = 0

    //MARK: BaseScreenlet

    override public func createInteractor(name name: String?, sender: AnyObject?) -> Interactor? {

        let view = self.screenletView as! AddBookmarkView_default

        let interactor = AddBookmarkInteractor(screenlet: self,
                                           folderId: folderId,
                                           title: view.title!,
                                           url: view.URL!)

        //Called when the Interactor's server call finishes succesfully
        interactor.onSuccess = {
            let bookmarkName = interactor.resultBookmarkInfo!["name"] as! String
            print("Bookmark \"\(bookmarkName)\" saved!")
        }

        //Called when the Interactor's server call fails
        interactor.onFailure = { _ in
            print("An error occurred saving the bookmark")
        }

        return interactor
    }

}

For reference, the sample Add Bookmark Screenlet’s final code is here on GitHub.

You’re done! Your Screenlet is a ready-to-use component that you can add to your storyboard. You can even package it to contribute to the Screens project or distribute it with CocoaPods. Now you know how to create iOS Screenlets!

Supporting Multiple Themes in Your Screenlet

Adding Screenlet Actions

Create and Use a Connector with Your Screenlet

Add a Screenlet Delegate

Using and Creating Progress Presenters

Creating and Using Your Screenlet’s Model Class

Using Screenlets in iOS Apps

Architecture of Liferay Screens for iOS

Creating iOS Themes

Creating Android Screenlets

« Architecture of Liferay Screens for iOSSupporting Multiple Themes in Your Screenlet »
¿Fue útil este artículo?
Usuarios a los que les pareció útil: 0 de 0