The Screenlets that come with Liferay Screens cover common use cases for mobile apps that use Liferay. They authenticate users, interact with Dynamic Data Lists, view assets, and more. However, what if there’s no Screenlet for your specific use case? No sweat! 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:
-
Determine your Screenlet’s location. Where you create your Screenlet depends on how you’ll use it.
-
Create the Screenlet’s UI (its View). Although this tutorial presents all the information you need to create a View for your Screenlet, you may first want to learn how to create a View. For more information on Views in general, see the tutorial on using Views with Screenlets.
-
Create the Screenlet’s Interactor. Interactors are Screenlet components that make server calls.
-
Define the Screenlet’s attributes. These are the XML attributes the app developer can set when inserting the Screenlet’s XML. These attributes control aspects of the Screenlet’s behavior. You’ll add functionality to these attributes in the Screenlet class.
-
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.
To understand the components that make up a Screenlet, you should first learn the architecture of Liferay Screens for Android.
Without further ado, let the Screenlet creation begin!
Determining Your Screenlet’s Location
Where you should create your Screenlet depends on how you plan to use it. If you don’t plan to reuse your Screenlet in another app or don’t want to redistribute it, create it in a new package inside your Android app project. This lets you reference and access Liferay Screens’s core, in addition to all the View Sets you may have imported.
If you want to reuse your Screenlet in another app, create it in a new Android application module. The tutorial Packaging Android Screenlets explains how to do this. When your Screenlet’s project is in place, you can start by creating the Screenlet’s UI.
Creating the Screenlet’s UI
In Liferay Screens for Android, Screenlet UIs are called Views. Every Screenlet must have at least one View. A View consists of the following components:
-
The View Model interface: defines the methods the View needs to update the UI.
-
A layout XML file: defines the UI components that the View presents to the end user.
-
A View class: renders the UI, handles user interactions, and communicates with the Screenlet class. The View class implements the View Model interface.
-
The Screenlet class: Although technically part of a View, the Screenlet class depends on all the other Screenlet components. You therefore won’t create the Screenlet class until the end of this tutorial.
The first items to create for a Screenlet’s View are its View Model interface and layout. The following steps explain how:
-
To define the methods that every Screenlet’s View class must implement, Screens provides the
BaseViewModel
interface. Your View Model interface should extendBaseViewModel
to define any additional methods needed by your Screenlet. This includes any getters and setters for the attributes you want to use.For example, Add Bookmark Screenlet needs attributes for each bookmark’s URL and title. Its View Model interface,
AddBookmarkViewModel
, therefore, defines getters and setters for these attributes:public interface AddBookmarkViewModel extends BaseViewModel { String getURL(); void setURL(String value); String getTitle(); void setTitle(String value); }
-
Define your Screenlet’s UI by writing a standard Android layout XML file. The layout’s root element should be the fully qualified class name of your Screenlet’s View class. You’ll create that class in the next step, but determine its name now and name the layout’s root element after it. Finally, add any UI elements your View needs.
For example, Add Bookmark Screenlet’s layout needs two text fields: one for entering a bookmark’s URL and one for entering its title. The layout also needs a button for saving the bookmark. The Screenlet defines this UI in its
bookmark_default.xml
layout file:<?xml version="1.0" encoding="utf-8"?> <com.your.package.AddBookmarkView xmlns:android="http://schemas.android.com/apk/res/android" style="@style/default_screenlet"> <EditText android:id="@+id/url" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="15dp" android:hint="URL Address" android:inputType="textUri"/> <EditText android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="15dp" android:hint="Title"/> <Button android:id="@+id/add_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Add Bookmark"/> </com.your.package.AddBookmarkView>
Next, you’ll create your Screenlet’s View class.
Creating the Screenlet’s View Class
Your Screenlet needs a View class to support the layout you just created. This
class must extend an Android layout class (e.g. LinearLayout
, ListView
),
implement your View Model interface, and implement a separate listener interface
to handle user actions. Follow these steps to create this View class:
-
Create a View class that extends the Android layout class appropriate for your Screenlet’s UI. For example, Add Bookmark Screenlet renders its UI components in a single column, so its View class (
AddBookmarkView
) extends Android’sLinearLayout
. Your View class’s constructors should call the parent layout class’s constructors. For example,AddBookmarkView
’s constructors call those ofLinearLayout
:public AddBookmarkView(Context context) { super(context); } public AddBookmarkView(Context context, AttributeSet attributes) { super(context, attributes); } public AddBookmarkView(Context context, AttributeSet attributes, int defaultStyle) { super(context, attributes, defaultStyle); }
-
Add instance variables for your View Model’s attributes and
BaseScreenlet
. For example, Add Bookmark Screenlet needs instance variables for a bookmark’s URL and title. Because all Screenlet classes extend theBaseScreenlet
class, aBaseScreenlet
variable in your View class ensures that your View always has a reference to the Screenlet. For example, here areAddBookmarkView
’s instance variables:private EditText urlText; private EditText titleText; private BaseScreenlet screenlet;
-
Implement your View Model interface. Implement your View Model’s getter and setter methods to get and set the inner value of each component, respectively. For example, here’s
AddBookmarkView
’s implementation ofAddBookmarkViewModel
:public String getURL() { return urlText.getText().toString(); } public void setURL(String value) { urlText.setText(value); } public String getTitle() { return titleText.getText().toString(); } public void setTitle(String value) { titleText.setText(value); }
-
Implement a listener interface to handle user actions in the Screenlet. For example, Add Bookmark Screenlet must detect when the user presses the save button. The
AddBookmarkView
class enables this by implementing Android’sView.OnClickListener
interface, which defines a single method:onClick
. The Screenlet’sonClick
implementation gets a reference to the Screenlet and calls itsperformUserAction()
method (you’ll createperformUserAction()
in the Screenlet class shortly):public void onClick(View v) { AddBookmarkScreenlet screenlet = (AddBookmarkScreenlet) getParent(); screenlet.performUserAction(); }
You can set the listener to the appropriate UI element by implementing an
onFinishInflate()
method. This method should also retrieve and assign any other UI elements from your layout. For example, theonFinishInflate()
implementation inAddBookmarkView
retrieves the URL and title attributes from the layout, and sets them to theurlText
andtitleText
variables, respectively. This method then retrieves the button from the layout and sets this View class as the button’s click listener:protected void onFinishInflate() { super.onFinishInflate(); urlText = (EditText) findViewById(R.id.url); titleText = (EditText) findViewById(R.id.title_bookmark); Button addButton = (Button) findViewById(R.id.add_button); addButton.setOnClickListener(this); }
-
Implement the
BaseViewModel
interface’s methods:showStartOperation
,showFinishOperation
,showFailedOperation
,getScreenlet
, andsetScreenlet
. In theshow*Operation
methods, you can log what happens in your Screenlet when the server operation starts, finishes successfully, or fails, respectively. In thegetScreenlet
andsetScreenlet
methods, you must get and set theBaseScreenlet
variable, respectively. This ensures that the View always has a Screenlet reference. For example, Add Bookmark Screenlet implements these methods as follows:@Override public void showStartOperation(String actionName) { } @Override public void showFinishOperation(String actionName) { LiferayLogger.i("Add bookmark successful"); } @Override public void showFailedOperation(String actionName, Exception e) { LiferayLogger.e("Could not add bookmark", e); } @Override public BaseScreenlet getScreenlet() { return screenlet; } @Override public void setScreenlet(BaseScreenlet screenlet) { this.screenlet = screenlet; }
Note that although you must implement the
show[something]Operation
methods, you can leave their implementations empty if you don’t need to take any specific action.
Click here
to see the complete example AddBookmarkView
class.
Great! Your View class is finished. Now you’re ready to create your Screenlet’s Interactor class.
Creating the Screenlet’s Interactor
A Screenlet’s Interactor makes the service call to retrieve the data you need from a Liferay instance. An Interactor is made up of several components:
-
The event class. This class lets you handle communication between the Screenlet’s components via event objects that contain the server call’s results. Screens uses the EventBus library for this. Screens supplies the
BasicEvent
class andBaseListEvent
class for communicatingJSONObject
andJSONArray
results within Screenlets, respectively. You can create your own event classes by extendingBasicEvent
. You should create your own event classes when you must communicate objects other thanJSONObject
orJSONArray
. The example Add Bookmark Screenlet only needs to communicateJSONObject
instances, so it usesBasicEvent
. -
The listener interface. This defines the methods the app developer needs to respond to the Screenlet’s behavior. For example, Login Screenlet’s listener defines the
onLoginSuccess
andonLoginFailure
methods. Screens calls these methods when login succeeds or fails, respectively. By implementing these methods in the activity or fragment class that contains the Screenlet, the app developer can respond to login success and failure. Similarly, the example Add Bookmark Screenlet’s listener interface defines two methods: one for responding to the Screenlet’s failure to add a bookmark and one for responding to its success to add a bookmark:public interface AddBookmarkListener { void onAddBookmarkFailure(Exception exception); void onAddBookmarkSuccess(); }
-
The Interactor class. This class must extend Screens’s
BaseRemoteInteractor
with your listener and event as type arguments. The listener lets the Interactor class send the server call’s results to any classes that implement the listener. In the implementation of the method that makes the server call, theexecute
method, you must use the Mobile SDK to make an asynchronous service call. This means you must get a session and then make the server call. You make the server call by creating an instance of the Mobile SDK service (e.g.,BookmarksEntryService
) that can call the Liferay service you need and then making the call. The Interactor class must also process the event object that contains the call’s results and then notify the listener of those results. You do this by implementing theonSuccess
andonFailure
methods to invoke the correspondinggetListener()
methods.For example, the
AddBookmarkInteractor
class is Add Bookmark Screenlet’s Interactor class. This class implements theexecute
method, which adds a bookmark to a folder in a Liferay instance’s Bookmarks portlet. This method first validates the bookmark’s URL and folder. It then calls thegetJSONObject
method to add the bookmark, and concludes by returning a newBasicEvent
object created from theJSONObject
. Theif
statement in thegetJSONObject
method checks the Liferay version so it can create the appropriateBookmarksEntryService
instance needed to make the server call. Regardless of the Liferay version, thegetSession()
method retrieves the existing session created by Login Screenlet upon successful login. The session’saddEntry
method makes the server call. The Screenlet calls theonSuccess
oronFailure
method to notify the listener of the server call’s success or failure, respectively. In either case, theBasicEvent
object contains the server call’s results. Since this Screenlet doesn’t retrieve anything from the server, however, there’s no need to process theBasicEvent
object in theonSuccess
method; calling the listener’sonAddBookmarkSuccess
method is sufficient. Here’s the complete code forAddBookmarkInteractor
:public class AddBookmarkInteractor extends BaseRemoteInteractor<AddBookmarkListener, BasicEvent> { @Override public BasicEvent execute(Object[] args) throws Exception { String url = (String) args[0]; String title = (String) args[1]; long folderId = (long) args[2]; validate(url, folderId); JSONObject jsonObject = getJSONObject(url, title, folderId); return new BasicEvent(jsonObject); } @Override public void onSuccess(BasicEvent event) throws Exception { getListener().onAddBookmarkSuccess(); } @Override public void onFailure(BasicEvent event) { getListener().onAddBookmarkFailure(event.getException()); } private void validate(String url, long folderId) { if (url == null || url.isEmpty() || !URLUtil.isValidUrl(url)) { throw new IllegalArgumentException("Invalid url"); } else if (folderId == 0) { throw new IllegalArgumentException("folderId not set"); } } @NonNull private JSONObject getJSONObject(String url, String title, long folderId) throws Exception { if (LiferayServerContext.isLiferay7()) { return new BookmarksEntryService(getSession()).addEntry(LiferayServerContext.getGroupId(), folderId, title, url, "", null); } else { return new com.liferay.mobile.android.v62.bookmarksentry.BookmarksEntryService( getSession()).addEntry(LiferayServerContext.getGroupId(), folderId, title, url, "", null); } } }
Sweetness! Your Screenlet’s Interactor is done. Next, you’ll create the Screenlet class.
Defining Screenlet Attributes in Your App
Before creating the Screenlet class, you should define its attributes. These are the attributes the app developer can set when inserting the Screenlet’s XML in an activity or fragment layout. For example, to use Login Screenlet, the app developer could insert the following Login Screenlet XML in an activity or fragment layout:
<com.liferay.mobile.screens.auth.login.LoginScreenlet
android:id="@+id/login_screenlet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:basicAuthMethod="email"
app:layoutId="@layout/login_default"
/>
The app developer can set the liferay
attributes basicAuthMethod
and
layoutId
to set Login Screenlet’s authentication method and View,
respectively. The Screenlet class reads these settings to enable the appropriate
functionality.
When creating a Screenlet, you can define the attributes you want to make
available to app developers. You do this in an XML file inside your Android
project’s res/values
directory. For example, Add Bookmark Screenlet’s
attributes are defined in the
Screenlet’s bookmark_attrs.xml
file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AddBookmarkScreenlet">
<attr name="layoutId"/>
<attr name="folderId"/>
<attr name="defaultTitle" format="string"/>
</declare-styleable>
</resources>
This defines the attributes layoutId
, folderId
, and defaultTitle
. Add
Bookmark Screenlet’s Screenlet class adds functionality to these attributes.
Here’s a brief description of what each does:
-
layoutId
: Sets the View that displays the Screenlet. This functions the same as thelayoutId
attribute in Liferay’s existing Screenlets. -
folderId
: Sets the folder ID in the Bookmarks portlet where the Screenlet adds bookmarks. -
defaultTitle
: Sets each Bookmark’s default title.
Now that you’ve defined your Screenlet’s attributes, you’re ready to create the Screenlet class.
Creating the Screenlet Class
The Screenlet class is the central hub of a Screenlet. It contains attributes for configuring the Screenlet’s behavior, a reference to the Screenlet’s View, 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).
To make all this possible, your Screenlet class must implement the Interactor’s
listener interface and extend
Screens’s BaseScreenlet
class
with the View Model interface and Interactor class as type arguments. Your
Screenlet class should also contain instance variables and accompanying getters
and setters for the listener and any other attributes that the app developer
needs to access. For constructors, you can call
BaseScreenlet
’s constructors.
For example,
Add Bookmark Screenlet’s Screenlet class
extends BaseScreenlet<AddBookmarkViewModel, AddBookmarkInteractor>
and
implements AddBookmarkListener
. It also contains instance variables for
AddBookmarkListener
and the bookmark’s folder ID, and getters and setters for
these variables. Also note the constructors call BaseListScreenlet
’s
constructors:
public class AddBookmarkScreenlet extends
BaseScreenlet<AddBookmarkViewModel, AddBookmarkInteractor>
implements AddBookmarkListener {
private long folderId;
private AddBookmarkListener listener;
public AddBookmarkScreenlet(Context context) {
super(context);
}
public AddBookmarkScreenlet(Context context, AttributeSet attributes) {
super(context, attributes);
}
public AddBookmarkScreenlet(Context context, AttributeSet attributes, int defaultStyle) {
super(context, attributes, defaultStyle);
}
public long getFolderId() {
return folderId;
}
public void setFolderId(long folderId) {
this.folderId = folderId;
}
public AddBookmarkListener getListener() {
return listener;
}
public void setListener(AddBookmarkListener listener) {
this.listener = listener;
}
...
Next, implement the Screenlet’s listener methods. This lets the Screenlet class receive the server call’s results and thus act as the listener. These methods should communicate the server call’s results to the View (via the View Model) and any other listener instances (via the Screenlet class’s listener instance). For example, here are Add Bookmark Screenlet’s listener method implementations:
public void onAddBookmarkSuccess() {
getViewModel().showFinishOperation(null);
if (listener != null) {
listener.onAddBookmarkSuccess();
}
}
public void onAddBookmarkFailure(Exception e) {
getViewModel().showFailedOperation(null, e);
if (listener != null) {
listener.onAddBookmarkFailure(e);
}
}
These methods are called when the server call succeeds or fails, respectively.
They first use getViewModel()
to get a View Model instance and then call the
BaseViewModel
methods showFinishOperation
and showFailedOperation
to send
the server call’s results to the View. The showFinishOperation
call sends
null
because a successful server call to add a bookmark doesn’t return any
objects. If a successful server call in your Screenlet returns any objects you
need to display, then you should send them in this showFinishOperation
call.
The showFailedOperation
call sends the Exception
that results from a failed
server call to the View. This lets you display an informative error to the user.
The onAddBookmarkSuccess
and onAddBookmarkFailure
implementations then call
the listener instance’s method of the same name. This sends the server call’s
results to any other classes that implement the listener interface, such as the
activity or fragment that uses the Screenlet.
Next, you must implement BaseScreenlet
’s abstract methods:
-
createScreenletView
: Reads the app developer’s Screenlet attribute settings, and inflates the View. You’ll use an AndroidTypedArray
to retrieve the attribute settings. You should set the attribute values to the appropriate variables, and set any default values you need to display via a View Model reference.For example, Add Bookmark Screenlet’s
createScreenletView
method gets the app developer’s attribute settings via aTypedArray
. This includes thelayoutId
,defaultTitle
, andfolderId
attributes. ThelayoutId
is used to inflate a View reference (view
), which is then cast to a View Model instance (viewModel
). The View Model instance’ssetTitle
method is then called withdefaultTitle
to set the bookmark’s default title. The method concludes by returning the View reference.@Override protected View createScreenletView(Context context, AttributeSet attributes) { TypedArray typedArray = context.getTheme() .obtainStyledAttributes(attributes, R.styleable.AddBookmarkScreenlet, 0, 0); int layoutId = typedArray.getResourceId(R.styleable.AddBookmarkScreenlet_layoutId, 0); View view = LayoutInflater.from(context).inflate(layoutId, null); String defaultTitle = typedArray.getString(R.styleable.AddBookmarkScreenlet_defaultTitle); folderId = castToLong(typedArray.getString(R.styleable.AddBookmarkScreenlet_folderId)); typedArray.recycle(); AddBookmarkViewModel viewModel = (AddBookmarkViewModel) view; viewModel.setTitle(defaultTitle); return view; }
-
createInteractor
: Instantiates the Screenlet’s Interactor. For example, Add Bookmark Screenlet’screateInteractor
method calls theAddBookmarkInteractor
constructor to create a new instance of this Interactor:@Override protected AddBookmarkInteractor createInteractor(String actionName) { return new AddBookmarkInteractor(getScreenletId()); }
-
onUserAction
: Retrieves any data the user has entered in the View, and starts the Screenlet’s server operation via an Interactor instance. If your Screenlet doesn’t take user input, this method only needs to do the latter.The example Add Bookmark Screenlet takes user input (the bookmark’s URL and title), so its
onUserAction
method must retrieve this data. This method does so via a View Model instance it retrieves with thegetViewModel()
method. TheonUserAction
method starts the server operation by calling the Interactor’sstart
method with the user input. Note that the Interactor inherits thestart
method from theBaseInteractor
class. Invoking thestart
method causes the Interactor’sexecute
method to run in a background thread:@Override protected void onUserAction(String userActionName, AddBookmarkInteractor interactor, Object... args) { AddBookmarkViewModel viewModel = getViewModel(); String url = viewModel.getURL(); String title = viewModel.getTitle(); interactor.start(url, title, folderId); }
Nice! Your Screenlet is finished! You can now use it the same way you would any other. If you created your Screenlet in its own project, you can also package and distribute it via the Screens project, JCenter, or Maven Central.
To finish the Add Bookmark Screenlet example, the following section shows you
how to use this Screenlet. It also shows how you can set default attribute
values in an app’s server_context.xml
file. Although you may not need to do
this when using your Screenlets, it might come in handy on your way to becoming
a master of Screenlets.
Using Your Screenlet
To use any Screenlet, you must follow these general steps:
-
Insert the Screenlet’s XML in the activity or fragment layout you want the Screenlet to appear in. You can fine-tune the Screenlet’s behavior by setting the Screenlet XML’s attributes.
-
Implement the Screenlet’s listener in the activity or fragment class.
As an example of this, the Liferay Screens
Test App
uses Add Bookmark Screenlet. You can find the following Add Bookmark Screenlet
XML in the
Test App’s add_bookmark.xml
layout:
<com.liferay.mobile.screens.bookmark.AddBookmarkScreenlet
android:id="@+id/bookmark_screenlet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:folderId="@string/bookmark_folder"
app:layoutId="@layout/bookmark_default" />
Note that the layout specified by app:layoutId
(bookmark_default
)
matches the layout file of the Screenlet’s View (bookmark_default.xml
). This
is how you specify the View that displays your Screenlet. For example, if Add
Bookmark Screenlet had another View defined in a layout file named
bookmark_awesome.xml
, you could use that layout by specifying
@layout/bookmark_awesome
as the app:layoutId
attribute’s value.
Also note that the app:folderId
attribute specifies
@string/bookmark_folder
as the bookmark folder’s ID. This is an alternative
way of specifying an attribute’s value. Instead of specifying the value
directly, the Test App specifies the value in its
server_context.xml
file:
...
<string name="bookmark_folder">20622</string>
...
This name
attribute’s value, bookmark_folder
is then used in the Screenlet
XML to set the app:folderId
attribute to 20622
.
Great! Now you know how to use the Screenlets you create. You also know a convenient way to specify default values for a Screenlet’s attributes.
Related Topics
Using Screenlets in Android Apps