Getting started with Facebook Litho

These are my understandings on the recently open sourced Android library Litho from Facebook.

What is it?

From their website, Litho is a “declarative UI framework for Android”, which means it uses a set of declarative APIs to create UI components. So we mention the components needed along with their properties. And everything else from there, UI component creation and display, are taken care of by the framework.

OK, so what are the advantages?

  • Litho does the measure and layout passes ahead of time asynchronously without blocking the UI thread.
  • Litho uses Yoga internally which reduces the number of ViewGroups that a normal XML UI may contain. So this result in flatter hierarchies and improved performance.
  • Another benefit is it recycles each Views individually, so in a list, if a View goes off screen, it can be reused anywhere or made into a new UI element eliminating the need to create multiple view types in a list.

Disadvantages, if any?

Yeah, Litho was primarily built with RecyclerViews in mind. If you have a very complex RecyclerView with several view types, or if you are facing frame drops while scrolling, then definitely try Litho. But if you have a dynamic UI or UI with several animations in it then better use the default Android APIs or wait until APIs for that becomes available.(As of Litho 0.2.0)

Alright! Show me some code!

Adding Litho to your project

In build.gradle file add these dependencies:

  // Litho  
compile 'com.facebook.litho:litho-core:0.2.0'
compile 'com.facebook.litho:litho-widget:0.2.0'
provided 'com.facebook.litho:litho-annotations:0.2.0'

annotationProcessor 'com.facebook.litho:litho-processor:0.2.0'

// SoLoader
compile 'com.facebook.soloader:soloader:0.2.0'

Now initialize SOLoader in your Application class:

@Override
public void onCreate() {
super.onCreate();

SoLoader.init(this, false);
}

Remember to add your Application class name in your Manifest. Or else you will get this exception.

java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: SoLoader.init() not yet called

Create a Text component

To display a simple TextView, in your onCreate() of your Activity:

final ComponentContext componentContext = new ComponentContext(this);

Component<Text> textComponent = Text.create(componentContext)
.text("Hello World")
.textColor(Color.GREEN)
.textSizeDip(28)
.verticalGravity(VerticalGravity.CENTER)
.textAlignment(Layout.Alignment.ALIGN_CENTER)
.build();

final LithoView lithoView = LithoView.create(
this,
textComponent);

setContentView(lithoView);

Here we create a Text component with props like text, color, size and alignment. We then need a ViewGroup to add the Text to. So we use LithoView and add the Text component to it.

You will see the “Hello World” text in green color at the center of the layout.

Spec class

To display multiple Views, we can create a Spec class where we can define our layout components. Litho generates a ComponentLifecycle subclass class from the Spec class with necessary methods in it. For example, if the Spec is ListItemSpec then Litho generates ListItem class at compile time which we can pass to LithoView. Litho identifies the Spec class using the @LayoutSpec annotation. In the onCreateLayout method we will return a ComponentLayout with all the child views in it, if any.

@LayoutSpec
public class ListItemSpec {

@OnCreateLayout
static ComponentLayout onCreateLayout(ComponentContext c) {
return Column.create(c)
.paddingDip(YogaEdge.ALL, 16)
.backgroundColor(Color.WHITE)
.child(
Text.create(c)
.text("Hello World")
.textSizeSp(40)
)
.child(
Text.create(c)
.text("Litho tutorial")
.textSizeSp(20)
)
.build();
}

}

In your Activity’s onCreate() pass the ListItem Component as a parameter in LithoView.

final LithoView lithoView = LithoView.create(
this,
ListItem.create(componentContext).build());
setContentView(lithoView);

Create a list of items

Similarly to create a static list of items, use Recycler component. Recycler is similar to RecyclerView but in Litho all layout is performed in separate thread. A RecyclerBinder is used similar to LayoutManager which provides the layout for each items.

final ComponentContext componentContext = new ComponentContext(this);

final RecyclerBinder recyclerBinder = new RecyclerBinder(
componentContext,
new LinearLayoutInfo(this, OrientationHelper.VERTICAL, false));
// Data
for (int i = 0; i < 16; i++) {
recyclerBinder.insertItemAt(
i,
ComponentInfo.create()
.component(ListItem.create(context).build())
.build());
}
final Component<Recycler> recyclerComponent = Recycler.create(componentContext)
.binder(recyclerBinder)
.build();

Here we create a RecyclerBinder with vertical orientation and pass it to Recycler component. We then loop through the data list to insert each item into the binder. At each position the RecyclerView gets notified of new item. If the item’s position falls within the visibility range, then the layout gets computed on the UI thread immediately. We use ComponentInfo to add our ListItem component to the binder.

Props

To send a data to a Component, we use prop. A prop can be anything — a data that you want to display on a UI, or a property of a view that you want to override, etc. Props can be added using the @Prop annotation. The inputs we provide as props are immutable and arer unidirectional meaning we can only pass the data to Component, but cannot retrieve it.

@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop String title,
@Prop String subTitle,
@Prop int color) {
return Column.create(c)
.paddingDip(YogaEdge.ALL, 16)
.backgroundColor(color)
.child(
Text.create(c)
.text(title)
.textSizeSp(32)
)
.child(
Text.create(c)
.text(subTitle)
.textSizeSp(20)
)
.build();
}

Set the title, subtitle and color for each item using @Prop.

recyclerBinder.insertItemAt(
i,
ComponentInfo.create()
.component(ListItem.create(context)
.title(title)
.subTitle(subTitle)
.color(
i % 2 == 0 ?
Color.WHITE :
Color.rgb(220, 220, 220)
)
.build())
.build());

This looks good. But how can I load a dynamic list of items asynchronously using Litho?

Loading data asynchronously into Recycler

Let’s create a list of phone numbers using Recycler. This list will show the contact’s name, phone number and a Uri of thumbnail photo.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

componentContext = new ComponentContext(this);

recyclerBinder = new RecyclerBinder(
componentContext,
new LinearLayoutInfo(this, OrientationHelper.VERTICAL, false));

Component<Recycler> recyclerComponent = Recycler.create(componentContext)
.binder(recyclerBinder)
.build();

addContents(recyclerBinder, componentContext, null);

final LithoView lithoView = LithoView.create(
this,
recyclerComponent);


setContentView(lithoView);

if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_READ_CONTACTS_PERMISSION);
return;
}

getLoaderManager().initLoader(0, null, this);

}

Here we declare the componentContext and recyclerBinder globally so that they can be accessible once the data is available. I use a CursorLoader to asynchronously retrieve the list of phone numbers as Cursor. Notice the method addContents(recyclerBinder, componentContext, null);

Here I pass the Cursor as null before loading the data. So I show a ProgressBar

when data is null and when data gets available in the onLoadFinished, addContents(recyclerBinder, componentContext, data); is called. So the ProgressBar gets removed from the RecyclerBinder and the list of phone numbers are inserted.

private static void addContents(RecyclerBinder recyclerBinder, ComponentContext context, Cursor data) {

if (data == null) {
recyclerBinder.insertItemAt(
0,
ProgressBar.create(context)
.build());
return;
}

if (recyclerBinder.getItemCount() == 1) {
recyclerBinder.removeItemAt(0);
}

data.moveToFirst();
for (int i = 0; i < data.getCount(); i++) {
String name = data.getString(
data.getColumnIndex(
data.getColumnName(DISPLAY_NAME_INDEX)));
String number = data.getString(
data.getColumnIndex(
data.getColumnName(NUMBER_INDEX)));
String photo = data.getString(
data.getColumnIndex(
data.getColumnName(PHOTO_THUMBNAIL_URI_INDEX)));

recyclerBinder.insertItemAt(
i,
ComponentInfo.create()
.component(Card.create(context)
.content(
ListItem.create(context)
.name(name)
.phoneNumber(number)
.photo(photo)
.build()
)
.build())
.build());

data.moveToNext();

}

}

The ListItemSpec will look like this:

@LayoutSpec
public class ListItemSpec {

@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop String name,
@Prop String phoneNumber,
@Prop String photo) {

final ComponentLayout column = Column.create(c)
.marginDip(YogaEdge.LEFT, 16)
.child(
Text.create(c)
.text(name)
.textSizeSp(24)
)
.child(
Text.create(c)
.text(phoneNumber)
.textSizeSp(16)
)
.build();

final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(photo)
.build();

return Row.create(c)
.backgroundAttr(android.R.attr.selectableItemBackground)
.paddingDip(YogaEdge.ALL, 16)
.child(
FrescoImage.create(c)
.controller(controller)
.failureImageRes(R.drawable.ic_account_box_black_48dp)
.placeholderImageRes(R.drawable.ic_account_box_black_48dp)
.withLayout()
.backgroundColor(Color.GRAY)
.widthDip(80)
.heightDip(80)
)
.child(column)
.clickHandler(ListItem.onClick(c))
.build();
}

@OnEvent(ClickEvent.class)
static void onClick(ComponentContext c,
@FromEvent View view) {

}

}

Notice how photo is shown. Since the default Image Component does not take image Uri as a parameter, we use FrescoImage provided by Litho. Just add the following dependency:

compile 'com.facebook.litho:litho-fresco:0.2.0'

Initialize it in the Application class:

Fresco.initialize(this);

We use a DraweeController to retrieve the image. Then it is displayed in FrescoImage. We can also provide placeholder image. If you don’t want to use Fresco, you have to write your own custom view and the image retrieving boilerplates. You can use Mount Spec to create custom Views.

Finally our UI will look like this:

Final thoughts

The APIs and the documentations are excellent and very easy to understand for even a novice to get started with Litho.

You can find the sample code mentioned here on Github.