# Creating Screenlets in Liferay Screens for Android
## Introduction
This document explains the steps required to create your own screenlets in Liferay Screens for Android. Before proceeding, you may want to read the [Architecture Guide](architecture.md) to understand the concepts underlying screenlets. You may also want to read the guide [How to Create Your Own View](view_creation.md) to support the new screenlet from the Default or other view sets.
The steps below walk you through creating an example screenlet for bookmarks that has the following features:
- Allows the user to enter a URL and title in a text box.
- When the user touches the submit button, the URL and title are sent to the Liferay instance's Bookmark service to be saved.
Now that you know the basic ideas behind Screenlets and have a goal for the screenlet you'll create here, it's time to get started!
## Where Should You Create Your New Screenlet?
If you don't plan to reuse your screenlet in another app, or if you don't want to redistribute it, the best place to create it is in a new package inside your project. This way you can reference and access all the viewsets you've imported and the core of Liferay Screens.
If you want to reuse your screenlet in another app, you need to create it in a new Android application module. The steps for creating such a module are referenced at the end of this document. This module needs to include Liferay Screens as a dependency, as well as the viewsets you're using.
## Creating Your Screenlet
1. Create a new interface called `AddBookmarkViewModel`. This is for adding the attributes to show in the view. In this case, the attributes are `url` and `title`. Any screenlet view must implement this interface.
```java
public interface AddBookmarkViewModel extends BaseViewModel {
String getURL();
void setURL(String value);
String getTitle();
void setTitle(String value);
}
```
2. Build your UI using a layout XML file. Put in two `EditText` tags: one for the URL and another for the title. Also, add a `Button` tag to let the user save the bookmark. Note that the root element is a custom class. You'll create this class in the next step.
```xml
```
At this point, the graphical layout viewer in Android Studio should look like this:
![An app based on Liferay Screens.](images/addbookmark_view.png)
3. Create a new custom view class called `AddBookmarkView`, that extends `LinearLayout` and implements `AddBookmarkViewModel`. This new class is where you implement the UI using the layout XML file from the previous step.
```java
public class AddBookmarkView
extends LinearLayout implements AddBookmarkViewModel {
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);
}
@Override
public void showStartOperation(String actionName) {
}
@Override
public void showFinishOperation(String actionName) {
}
@Override
public void showFailedOperation(String actionName, Exception e) {
}
private EditText _urlText;
private EditText _titleText;
}
```
4. In the `onFinishInflate` method, get the reference to the components. Then create the getters and setters using the inner value of the components:
```java
@Override
protected void onFinishInflate() {
super.onFinishInflate();
_urlText = (EditText) findViewById(R.id.url);
_titleText = (EditText) findViewById(R.id.title);
}
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);
}
```
5. Create the interactor class. It's responsible for sending the bookmark to the Liferay instance (or any other backend). Note that it's a good practice to use [IoC](http://en.wikipedia.org/wiki/Inversion_of_control) in your interactor classes. This way, anyone can provide a different implementation without breaking the code. The `Interactor` base class also needs a parameter that represents the type of listener to notify. This is defined here (we will implement the `AddBookmarkListener` later):
```java
public interface AddBookmarkInteractor extends Interactor {
void addBookmark(String url, String title, Integer folderId) throws Exception;
}
```
```java
public class AddBookmarkInteractorImpl
extends BaseRemoteInteractor
implements AddBookmarkInteractor {
public AddBookmarkInteractorImpl(int targetScreenletId) {
super(targetScreenletId);
}
public void addBookmark(String url, String title, Integer folderId) {
// 1. Validate input
if (url == null || url.isEmpty()) {
throw new IllegalArgumentException("Invalid url");
}
// 2. Call the service asynchronously.
// Notify when the request ends using the EventBus
}
public void onEvent(JSONObjectEvent event) {
if (!isValidEvent(event)) {
return;
}
if (event.isFailed()) {
getListener().onAddBookmarkFailure(event.getException());
}
else {
getListener().onAddBookmarkSuccess();
}
}
}
```
Pay special attention to the second step in the `addBookmark` method. When the request ends, make sure you post an event into the bus using `EventBusUtil.post(event)`, where `event` is a `JSONObjectEvent` object containing the `targetScreenletId` together with either the result or the exception. Every interactor should also implement the `onEvent` method. This method is invoked by the `EventBus` and calls the registered listener. A good example is the `AddBookmarkInteractorImpl`.
6. Our `AddBookmarkListener` interface will be really simple, just having two methods. For example:
```java
public interface AddBookmarkListener {
void onAddBookmarkFailure(Exception exception);
void onAddBookmarkSuccess();
}
```
7. Once your interactor is ready, you need to create the screenlet class. This is the cornerstone and entry point that your app developer sees and interacts with. In this example, this class is called `AddBookmarkScreenlet` and extends from `BaseScreenlet`. Again, this class needs to be parameterised with the interactor class. Since the screenlet is notified by the interactor when the asynchronous operation ends, you must implement the listener interface used by the interactor (`AddBookmarkListener`, in this case). Also, to notify the app, this class usually has another listener. This listener can be the same one you used in the interactor or a different one altogether (if you want different methods or signatures). You could even notify the app using a different mechanism such as the Event Bus, Android's `BroadcastReceiver`, or others. Note that the implemented interface methods call the view to modify the UI and the app's listener to allow the app to perform any action:
```java
public class AddBookmarkScreenlet
extends BaseScreenlet
implements AddBookmarkListener {
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 void onAddBookmarkSuccess() {
// Invoked from the interactor:
// Notify both the view and the app's listener
getViewModel().showFinishOperation(null);
if (_listener != null) {
_listener.onAddBookmarkSuccess();
}
}
public void onAddBookmarkFailure(Exception e) {
getViewModel().showFinishOperation(null);
if (_listener != null) {
_listener.onAddBookmarkFailure(e);
}
}
public void setListener(AddBookmarkListener listener) {
_listener = listener;
}
private AddBookmarkListener _listener;
}
```
8. You're almost finished! The next step is to implement the screenlet's abstract methods. First is the `createScreenletView` method. In this method you get attributes from the XML definition and either store them as class attributes or otherwise make use of them. Then inflate the view using the layout specified in the `liferay:layoutId` attribute. You can even configure the initial state of the view, using the attributes read.
```java
@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 = typedArray.getInteger(R.styleable.AddBookmarkScreenlet_folderId, 0);
typedArray.recycle();
AddBookmarkViewModel viewModel = (AddBookmarkViewModel) view;
viewModel.setTitle(defaultTitle);
return view;
}
private Integer _folderId;
```
The Second abstract method to implement is `createInteractor`. This is a factory method in which you have to create the corresponding interactor for a specific action name. Note that a single screenlet may have several interactions (use cases). Each interaction should therefore be implemented in a separate interactor. In this example there is only one interactor, so the object is created in the method. Alternatively, you can retrieve the instance by using your IoC framework. Also, you need to pass the `screenletId` (a number autogenerated by the `BaseScreenlet` class) to the constructor:
```java
protected AddBookmarkInteractor createInteractor(String actionName) {
return new AddBookmarkInteractorImpl(getScreenletId());
}
```
The third and final abstract method to implement is `onUserAction`. In this method, retrieve the data entered in the view and start the operation by using the supplied interactor and the data:
```java
@Override
protected void onUserAction(String userActionName, BookmarkInteractor interactor, Object... args) {
AddBookmarkViewModel viewModel = (AddBookmarkViewModel) getScreenletView();
String url = viewModel.getURL();
String title = viewModel.getTitle();
try {
interactor.addBookmark(url, title, _folderId);
}
catch (Exception e) {
onAddBookmarkFailure(e);
}
}
```
To be able to read the screenlet attributes you have to add an xml file that defines those attributes. An example for this `AddBookmarkScreenlet`:
```xml
```
9. The only thing left to do is to trigger the user action when the button is pressed. To do this, go back to the view and add a listener to the button:
```java
protected void onFinishInflate() {
super.onFinishInflate();
// same code as before
Button addButton = (Button) findViewById(R.id.add_button);
addButton.setOnClickListener(this);
}
public void onClick(View v) {
AddBookmarkScreenlet screenlet = (AddBookmarkScreenlet) getParent();
screenlet.performUserAction();
}
```
You also have to make `AddBookmarkView` implement `OnClickListener`.
Congratulations! Now you know how to create your own screenlets.
## Packaging Your Screenlets
If you want to distribute your screenlets for use in different projects, you should package them in a module (Android library). To use the screenlet, developers then add that module as a project dependency in their app.
Use the following steps to package your screenlets in a module:
1. Create a new Android module and configure the `build.gradle` file.
2. Configure dependencies between each module
3. Optionally, you can distribute the module by uploading it to jCenter or Maven Central.
The next sections detail these steps.
### Create a New Android Module
Fortunately, Android Studio has a menu option that automatically creates an Android module and adds it to your `settings.gradle` file. Go to *File* → *New* → *New Module* → *Android Library* (in *More Modules*) and enter a name for your new module. You don't need a new activity for the new module, so just use *Blank Activity*. Android Studio automatically creates a new `build.gradle` file (with an Android Library configuration) and adds the new module to the `settings.gradle` file.
If you prefer to do this manually, you need to create a new Android Library. This is essentially an Android app project with the gradle import set to `apply plugin: 'com.android.library'`. Use the [gradle file](https://github.com/liferay/liferay-screens/blob/master/android/viewsets/material/build.gradle) from the material viewset or Westeros app as an example.
After creating the module manually, you need to import it into your project by specifying its location in [`settings.gradle`](https://github.com/liferay/liferay-screens/tree/master/android/samples/settings.gradle). Here's an example of this configuration:
```groovy
include ':YOUR_MODULE_NAME'
project(':YOUR_MODULE_NAME').projectDir = new File(settingsDir, 'RELATIVE_ROUTE_TO_YOUR_MODULE')
```
### Configure Dependencies Between Each Module
Next, you need to configure your app to use the new module. To do so, add the following `compile` statement to the `dependencies` in your `build.gradle` file:
```groovy
dependencies {
compile project (':YOUR_MODULE_NAME')
...
}
```
Your module also needs the dependencies required to override the existing screenlets or create new ones. This usually means that you need to add Liferay Screens and the view sets you currently use as dependencies. To do so, add the following `compile` statement to the `dependencies` in your `build.gradle` file:
```groovy
dependencies {
compile 'com.liferay.mobile:liferay-screens:0.3.+'
...
}
```
### Upload the Module to jCenter or Maven Central
If you want to distribute your screenlet so that others can use it, you can upload it to jCenter or Maven Central. Use the [`build.gradle`](https://github.com/liferay/liferay-screens/blob/master/android/viewsets/westeros/build.gradle) file of the material or Westeros viewset as an example.
After entering your bintray api key, you can execute `gradlew bintrayupload` to upload your project to jCenter. When finished, your screenlet can be used as any other Android dependency. Developers just need to add the repository, artifact, groupId, and version to their gradle file.