A simple guide to recyclerlistview

Some time back I came across this video about how flipkart uses react native and their in-house Widgetised framework, when diving deep into I came across recyclerlistview and I really wanted to give it a try because it's much more efficient and no performance issues like flatlist

I was really amazed with recyclerlistview it helped me to define the layout in a better way but I felt that documentation isn't the best to start with, so I thought to write about it

Let's jump to recyclerlistview now

With recyclerlistview we need to attach meta data with our data, so we can control the layout with the help of it,the heart of recyclerlistview are DataProvider,LayoutProvider and rowRenderer.

  • DataProvider :: In simple words it's holds the data array for the list, while invoking it's constructor you need to pass whether the rows are same or not.

  • LayoutProvider :: So layout provider helps us to determine view port dimensions based on the data type, like "Avatar" type of data should take 50 width and height also it needs to return the " VIEW_TYPE " to help Row renderer.

  • rowRenderer :: So row renderer uses the " VIEW_TYPE " returned from Layout provider to determine which JSX to return .

Time for example

Screenshot (801).png

Before jumping to code let's decouple the UI we got grid items here , then we got one inner horizontal list which occupies the full width and inside the horizontal list we got more cards so let's make constant of these types so we can attach it in our data.

export const subItem = 'subItem';
export const ITEM = 'item';
export const innderData = 'innderData';

This is how array object looks like and here with the help of "type" field we will determine the layout dimensions.

[{
    type: ITEM,
    uri: 'uri',
    title: 'Aditya Pahilwani',
},
{
    type: subItem,
    uri: 'uri',
    title: 'Ellie',
}]

Now let's define our LayoutProvider in separate class so we can re-use it in our inner list also we will define some constant for " VIEW_TYPE " so we can use it across screens and make them single source of truth.

import { LayoutProvider } from 'recyclerlistview';
export const ViewTypes = {
  FULL: 0,
  GRID: 1,
  SUB_ITEM: 2,
};
export class LayoutUtil {
  static getWindowWidth() {
    return Math.round(Dimensions.get('window').width * 1000) / 1000;
  }
  static getLayoutProvider(dataProvider) {
    return new LayoutProvider();
  }
}

Now coming to the code we got one method getWindowWidth() which returns the width of device now we need to pass methods in LayoutProvider from which we can configure the view ports.

   static getLayoutProvider(dataProvider) {
    return new LayoutProvider((index) => {
        let type = dataProvider.getDataForIndex(index).type;
        if (type == ITEM) {
          return ViewTypes.GRID;
        } else if (type == subItem) {
          return ViewTypes.FULL;
        } else if (type == innderData) {
          return ViewTypes.SUB_ITEM;
        }
      },
      (type, dim) => {
        const width = LayoutUtil.getWindowWidth();
        let layoutHeight = 300;
        const innerCardHeight = layoutHeight - 50;
        const innerCardWidth = width;
        switch (type) {
          case ViewTypes.GRID:
            dim.width = width / 2;
            dim.height = layoutHeight;
            break;
          case ViewTypes.FULL:
            dim.width = width;
            dim.height = layoutHeight;
            break;
          case ViewTypes.SUB_ITEM:
            dim.width = innerCardWidth - 100;
            dim.height = innerCardHeight;
            break;
          default:
            dim.width = 0;
            dim.height = 0;
        }
      }
    );
  }

In first method of LayoutProvider we determine the "VIEW_TYPE" with the help of getDataForIndex(index) method from DataProvider and in the second method we get dim and type from layoutProvider to configure the dimensions based on "VIEW_TYPE" returned from first method.

Now as you can see for different type of data we return different type of dimensions.

  • ViewTypes.GRID :: returns dimension for grid items which takes up half width of device.
  • ViewTypes.FULL :: returns dimension for inner list area which takes up the whole width of device.
  • ViewTypes.SUB_ITEM :: returns dimension for innerList items.

Let's jump to stitch our config with UI

Now as we have configured view port dimension we need to use flex:1 inside our containers so that it can take whole space (note :: " if you give height and width manually in containers, it won't raise an error but it will remain inside the view port dimension boundary provided by layout provider " )

let's create an simple card which displays an image with text.

export default function Card(props) {
  const { uri, title, style } = props;
  return (
    <View style={[styles.container, style]}>
      <Image style={styles.logo} source={{ uri: uri }} />
      <Text style={styles.paragraph}>{title}</Text>
    </View>
  );
}

Finally let's use recyclerlistview

import { RecyclerListView, DataProvider } from "recyclerlistview";
import { LayoutUtil, ViewTypes } from "./utils/layoutUtil";
export default function App() {
  let dataProvider = new DataProvider((r1, r2) => {
    return r1 !== r2;
  });
  dataProvider = dataProvider.cloneWithRows(data);
  const margin = 3;
  let layoutProvider = LayoutUtil.getLayoutProvider(dataProvider);
  const rowRenderer = (type, data) => {
    const { uri, title, innerData } = data;
    switch (type) {
      case ViewTypes.GRID:
        return <Card uri={uri} title={title} style={{ marginHorizontal: 2 }} />;
      case ViewTypes.FULL:
        return <HorizontalRecylerList data={innerData} />;
      default:
        return null;
    }
  };

  return (
    <View style={styles.container}>
      <RecyclerListView
        contentContainerStyle={{ margin }}
        layoutProvider={layoutProvider}
        dataProvider={dataProvider}
        rowRenderer={rowRenderer}
      />
    </View>
  );
}

Now as you can see based on " VIEW_TYPE " returned from layoutProvider, rowRenderer returns the corresponding JSX

We forgot HorizontalRecylerList for the inner list

We got every config done already so let's write this component and it's gonna be simple and straight forward.

import {
  RecyclerListView,
  DataProvider,
} from 'recyclerlistview';
import Card from './card';
import { LayoutUtil, ViewTypes } from '../utils/layoutUtil';

export default function HorizontalRecylerList(props) {
  const { data } = props;
  let dataProvider = new DataProvider((r1, r2) => {
    return r1 !== r2;
  });
  dataProvider = dataProvider.cloneWithRows(data);
  let layoutProvider = LayoutUtil.getLayoutProvider(dataProvider);
  const rowRenderer = (type, data) => {
    const { uri, title } = data;
    switch (type) {
      case ViewTypes.SUB_ITEM:
        return (
          <Card uri={uri} title={title} style={{ marginHorizontal: 10 }} />
        );
      default:
        return null;
    }
  };

And it's a wrap !!

Thank you so much for sticking around and I know it's gonna be a little hard to understand what's going on but I tried my best to explain this in a simple manner.

Github repo

Snack Link