Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"homepage": "https://github.com/Flipkart/recyclerlistview",
"dependencies": {
"immutable": "^4.0.0-rc.12",
"lodash.debounce": "4.0.8",
"prop-types": "15.5.8",
"ts-object-utils": "0.0.5"
Expand Down
9 changes: 5 additions & 4 deletions src/core/RecyclerListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
import debounce = require("lodash.debounce");
import * as PropTypes from "prop-types";
import * as React from "react";
import { List } from "immutable";
import { ObjectUtil, Default } from "ts-object-utils";
import ContextProvider from "./dependencies/ContextProvider";
import { BaseDataProvider } from "./dependencies/DataProvider";
import { BaseDataProvider, ListBaseDataProvider } from "./dependencies/DataProvider";
import { Dimension, BaseLayoutProvider } from "./dependencies/LayoutProvider";
import CustomError from "./exceptions/CustomError";
import RecyclerListViewExceptions from "./exceptions/RecyclerListViewExceptions";
Expand Down Expand Up @@ -78,8 +79,8 @@ export interface OnRecreateParams {

export interface RecyclerListViewProps {
layoutProvider: BaseLayoutProvider;
dataProvider: BaseDataProvider;
rowRenderer: (type: string | number, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | null;
dataProvider: BaseDataProvider | ListBaseDataProvider;
rowRenderer: (type: string | number, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | List<JSX.Element> | null;
contextProvider?: ContextProvider;
renderAheadOffset?: number;
isHorizontal?: boolean;
Expand Down Expand Up @@ -671,7 +672,7 @@ RecyclerListView.propTypes = {
layoutProvider: PropTypes.instanceOf(BaseLayoutProvider).isRequired,

//Refer the sample
dataProvider: PropTypes.instanceOf(BaseDataProvider).isRequired,
dataProvider: PropTypes.oneOf([PropTypes.instanceOf(BaseDataProvider).isRequired, PropTypes.instanceOf(ListBaseDataProvider).isRequired]),

//Used to maintain scroll position in case view gets destroyed e.g, cases of back navigation
contextProvider: PropTypes.instanceOf(ContextProvider),
Expand Down
12 changes: 7 additions & 5 deletions src/core/StickyContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import * as React from "react";
import * as PropTypes from "prop-types";
import { StyleProp, View, ViewStyle } from "react-native";
import { List } from "immutable";
import RecyclerListView, { RecyclerListViewState, RecyclerListViewProps } from "./RecyclerListView";
import { ScrollEvent } from "./scrollcomponent/BaseScrollView";
import StickyObject, { StickyObjectProps } from "./sticky/StickyObject";
Expand All @@ -14,7 +15,7 @@ import CustomError from "./exceptions/CustomError";
import RecyclerListViewExceptions from "./exceptions/RecyclerListViewExceptions";
import { Layout } from "./layoutmanager/LayoutManager";
import { BaseLayoutProvider, Dimension } from "./dependencies/LayoutProvider";
import { BaseDataProvider } from "./dependencies/DataProvider";
import { BaseDataProvider, ListBaseDataProvider } from "./dependencies/DataProvider";
import { ReactElement } from "react";
import { ComponentCompat } from "../utils/ComponentCompat";
import { WindowCorrection } from "./ViewabilityTracker";
Expand All @@ -23,7 +24,8 @@ export interface StickyContainerProps {
children: RecyclerChild;
stickyHeaderIndices?: number[];
stickyFooterIndices?: number[];
overrideRowRenderer?: (type: string | number | undefined, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | null;
overrideRowRenderer?: (type: string | number | undefined,
data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | List<JSX.Element> | null;
applyWindowCorrection?: (offsetX: number, offsetY: number, winowCorrection: WindowCorrection) => void;
renderStickyContainer?: (stickyContent: JSX.Element, index: number, extendedState?: object) => JSX.Element | null;
style?: StyleProp<ViewStyle>;
Expand All @@ -35,10 +37,10 @@ export interface RecyclerChild extends React.ReactElement<RecyclerListViewProps>
export default class StickyContainer<P extends StickyContainerProps> extends ComponentCompat<P> {
public static propTypes = {};
private _recyclerRef: RecyclerListView<RecyclerListViewProps, RecyclerListViewState> | undefined = undefined;
private _dataProvider: BaseDataProvider;
private _dataProvider: BaseDataProvider | ListBaseDataProvider;
private _layoutProvider: BaseLayoutProvider;
private _extendedState: object | undefined;
private _rowRenderer: ((type: string | number, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | null);
private _rowRenderer: ((type: string | number, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | List<JSX.Element> | null);
private _stickyHeaderRef: StickyHeader<StickyObjectProps> | null = null;
private _stickyFooterRef: StickyFooter<StickyObjectProps> | null = null;
private _visibleIndicesAll: number[] = [];
Expand Down Expand Up @@ -204,7 +206,7 @@ export default class StickyContainer<P extends StickyContainerProps> extends Com
}

private _getRowRenderer = (): ((type: string | number, data: any, index: number, extendedState?: object)
=> JSX.Element | JSX.Element[] | null) => {
=> JSX.Element | JSX.Element[] | List<JSX.Element> | null) => {
return this._rowRenderer;
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/VirtualRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Point, LayoutManager } from "./layoutmanager/LayoutManager";
import ViewabilityTracker, { TOnItemStatusChanged, WindowCorrection } from "./ViewabilityTracker";
import { ObjectUtil, Default } from "ts-object-utils";
import TSCast from "../utils/TSCast";
import { BaseDataProvider } from "./dependencies/DataProvider";
import { BaseDataProvider, ListBaseDataProvider } from "./dependencies/DataProvider";

/***
* Renderer which keeps track of recyclable items and the currently rendered items. Notifies list view to re render if something changes, like scroll offset
Expand Down Expand Up @@ -248,7 +248,7 @@ export default class VirtualRenderer {
}

//Further optimize in later revision, pretty fast for now considering this is a low frequency event
public handleDataSetChange(newDataProvider: BaseDataProvider, shouldOptimizeForAnimations?: boolean): void {
public handleDataSetChange(newDataProvider: BaseDataProvider | ListBaseDataProvider, shouldOptimizeForAnimations?: boolean): void {
const getStableId = newDataProvider.getStableId;
const maxIndex = newDataProvider.getSize() - 1;
const activeStableIds: { [key: string]: number } = {};
Expand Down
150 changes: 115 additions & 35 deletions src/core/dependencies/DataProvider.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
import { List } from "immutable";
import { ObjectUtil } from "ts-object-utils";

/***
* You can create a new instance or inherit and override default methods
* Allows access to data and size. Clone with rows creates a new data provider and let listview know where to calculate row layout from.
*/
export abstract class BaseDataProvider {
public rowHasChanged: (r1: any, r2: any) => boolean;

// In JS context make sure stable id is a string
public getStableId: (index: number) => string;
private _firstIndexToProcess: number = 0;
private _size: number = 0;
private _data: any[] = [];
private _hasStableIds = false;
private _requiresDataChangeHandling = false;

constructor(rowHasChanged: (r1: any, r2: any) => boolean, getStableId?: (index: number) => string) {
export abstract class GenericDataProvider<T, K = keyof T> {
public rowHasChanged: (r1: T, r2: T) => boolean;
public getStableId: (index: number) => string; // In JS context make sure stable id is a string
protected _data: K; // Require Init Data

protected _firstIndexToProcess: number = 0;
protected _size: number = 0;
protected _hasStableIds = false;
protected _requiresDataChangeHandling = false;

constructor(initData: K,
rowHasChanged: (r1: T, r2: T) => boolean,
getStableId?: (index: number) => string ) {
this._data = initData;
this.rowHasChanged = rowHasChanged;
if (getStableId) {
this.getStableId = getStableId;
this.getStableId = getStableId;
this._hasStableIds = true;
} else {
this.getStableId = (index) => index.toString();
}
}

public abstract newInstance(rowHasChanged: (r1: any, r2: any) => boolean, getStableId?: (index: number) => string): BaseDataProvider;

public getDataForIndex(index: number): any {
return this._data[index];
}
public abstract newInstance(
rowHasChanged: (r1: T, r2: T) => boolean,
getStableId?: (index: number) => string ): GenericDataProvider<T, K>;
public abstract getDataForIndex(index: number): T | undefined;
public abstract cloneWithRows(newData: K,
firstModifiedIndex?: number): DataProvider | ListDataProvider;

public getAllData(): any[] {
public getAllData(): K {
return this._data;
}

Expand All @@ -50,35 +54,111 @@ export abstract class BaseDataProvider {
public getFirstIndexToProcessInternal(): number {
return this._firstIndexToProcess;
}
}

export abstract class BaseDataProvider extends GenericDataProvider<any, any[]> {
constructor(rowHasChanged: (r1: any, r2: any) => boolean,
getStableId?: (index: number) => string ) {
super([], rowHasChanged, getStableId);
}

public abstract newInstance(
rowHasChanged: (r1: any, r2: any) => boolean,
getStableId?: (index: number) => string ): BaseDataProvider;

public getDataForIndex(index: number): any | undefined {
return this._data[index];
}

//No need to override this one
//If you already know the first row where rowHasChanged will be false pass it upfront to avoid loop
public cloneWithRows(newData: any[], firstModifiedIndex?: number): DataProvider {
const dp = this.newInstance(this.rowHasChanged, this.getStableId);
const dp = this.newInstance(this.rowHasChanged, this.getStableId);
const newSize = newData.length;
const iterCount = Math.min(this._size, newSize);
if (ObjectUtil.isNullOrUndefined(firstModifiedIndex)) {
let i = 0;
for (i = 0; i < iterCount; i++) {
if (this.rowHasChanged(this._data[i], newData[i])) {
break;
}
}
dp._firstIndexToProcess = i;
} else {
dp._firstIndexToProcess = Math.max(Math.min(firstModifiedIndex, this._data.length), 0);
}

dp._firstIndexToProcess = ObjectUtil.isNullOrUndefined(firstModifiedIndex)
? this.getFirstIndexChange(newData, newSize)
: Math.max(Math.min(firstModifiedIndex, this._data.length), 0);

if (dp._firstIndexToProcess !== this._data.length) {
dp._requiresDataChangeHandling = true;
}
dp._data = newData;
dp._size = newSize;
return dp;
}

private getFirstIndexChange(newData: any[], newSize: number): number {
const iterCount = Math.min(this._size, newSize);
let i = 0;
for (i = 0; i < iterCount; i++) {
if (this.rowHasChanged(this._data[i], newData[i])) {
break;
}
}
return i;
}
}

export default class DataProvider extends BaseDataProvider {
public newInstance(rowHasChanged: (r1: any, r2: any) => boolean, getStableId?: ((index: number) => string) | undefined): BaseDataProvider {
return new DataProvider(rowHasChanged, getStableId);
export abstract class ListBaseDataProvider extends GenericDataProvider<any, List<any>> {
constructor(rowHasChanged: (r1: any, r2: any ) => boolean,
getStableId?: (index: number) => string ) {
super(List<any>([]), rowHasChanged, getStableId);
}

public abstract newInstance(
rowHasChanged: (r1: any, r2: any) => boolean,
getStableId?: (index: number) => string ): ListBaseDataProvider;

public getDataForIndex(index: number): any | undefined {
return this._data.get(index);
}

//No need to override this one
//If you already know the first row where rowHasChanged will be false pass it upfront to avoid loop
public cloneWithRows(newData: List<any>, firstModifiedIndex?: number): ListDataProvider {
const dp = this.newInstance(this.rowHasChanged, this.getStableId);
const newSize = newData.size;

dp._firstIndexToProcess = ObjectUtil.isNullOrUndefined(firstModifiedIndex)
? this.getFirstIndexChange(newData, newSize)
: Math.max(Math.min(firstModifiedIndex, this._data.size), 0);

if (dp._firstIndexToProcess !== this._data.size) {
dp._requiresDataChangeHandling = true;
}
dp._data = newData;
dp._size = newSize;
return dp;
}

private getFirstIndexChange(newData: List<any>, newSize: number): number {
if (this._data.equals(newData)) {
return this._size;
}

if (this._size > newSize) {
const sizeData = newData.setSize(this._size);
return (this._data as List<any>)
.findIndex((value, index) => this.rowHasChanged(value, sizeData.get(index)!));
} else {
const sizeData = this._data.setSize(newSize);
return (sizeData as List<any>)
.findIndex((value, index) => this.rowHasChanged(value, newData.get(index )!));
}
}
}

export default class DataProvider extends BaseDataProvider {
public newInstance(rowHasChanged: (r1: any, r2: any) => boolean,
getStableId?: ((index: number) => string ) | undefined): BaseDataProvider {
return new DataProvider(rowHasChanged, getStableId);
}
}

export class ListDataProvider extends ListBaseDataProvider {
public newInstance(rowHasChanged: (r1: any, r2: any) => boolean,
getStableId?: ((index: number) => string ) | undefined): ListBaseDataProvider {
return new ListDataProvider(rowHasChanged, getStableId);
}
}
10 changes: 6 additions & 4 deletions src/core/sticky/StickyObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import * as React from "react";
import { Animated, StyleProp, ViewStyle } from "react-native";
import { List } from "immutable";
import { Layout } from "../layoutmanager/LayoutManager";
import { Dimension } from "../dependencies/LayoutProvider";
import RecyclerListViewExceptions from "../exceptions/RecyclerListViewExceptions";
Expand All @@ -23,8 +24,9 @@ export interface StickyObjectProps {
getExtendedState: () => object | undefined;
getRLVRenderedSize: () => Dimension | undefined;
getContentDimension: () => Dimension | undefined;
getRowRenderer: () => ((type: string | number, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | null);
overrideRowRenderer?: (type: string | number | undefined, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | null;
getRowRenderer: () => ((type: string | number, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | List<JSX.Element> | null);
overrideRowRenderer?: (type: string | number | undefined,
data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | List<JSX.Element> | null;
renderContainer?: ((rowContent: JSX.Element, index: number, extendState?: object) => JSX.Element | null);
getWindowCorrection?: () => WindowCorrection;
}
Expand Down Expand Up @@ -229,12 +231,12 @@ export default abstract class StickyObject<P extends StickyObjectProps> extends
this._largestVisibleIndex = indicesArray[indicesArray.length - 1];
}

private _renderSticky(): JSX.Element | JSX.Element[] | null {
private _renderSticky(): JSX.Element | JSX.Element[] | List<JSX.Element> | null {
const _stickyData: any = this.props.getDataForIndex(this.currentStickyIndex);
const _stickyLayoutType: string | number = this.props.getLayoutTypeForIndex(this.currentStickyIndex);
const _extendedState: object | undefined = this.props.getExtendedState();
const _rowRenderer: ((type: string | number, data: any, index: number, extendedState?: object)
=> JSX.Element | JSX.Element[] | null) = this.props.getRowRenderer();
=> JSX.Element | JSX.Element[] | List<JSX.Element> | null) = this.props.getRowRenderer();
if (this.props.overrideRowRenderer) {
return this.props.overrideRowRenderer(_stickyLayoutType, _stickyData, this.currentStickyIndex, _extendedState);
} else {
Expand Down
5 changes: 3 additions & 2 deletions src/core/viewrenderer/BaseViewRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react";
import { List } from "immutable";
import { Dimension, BaseLayoutProvider } from "../dependencies/LayoutProvider";
import ItemAnimator from "../ItemAnimator";
import { LayoutManager } from "../layoutmanager/LayoutManager";
Expand All @@ -15,7 +16,7 @@ export interface ViewRendererProps<T> {
y: number;
height: number;
width: number;
childRenderer: (type: string | number, data: T, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | null;
childRenderer: (type: string | number, data: T, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | List<JSX.Element> | null;
layoutType: string | number;
dataHasChanged: (r1: T, r2: T) => boolean;
onSizeChanged: (dim: Dimension, index: number) => void;
Expand Down Expand Up @@ -61,7 +62,7 @@ export default abstract class BaseViewRenderer<T> extends ComponentCompat<ViewRe
this.props.itemAnimator.animateWillUnmount(this.props.x, this.props.y, this.getRef() as object, this.props.index);
}
protected abstract getRef(): object | null;
protected renderChild(): JSX.Element | JSX.Element[] | null {
protected renderChild(): JSX.Element | JSX.Element[] | List<JSX.Element> | null {
return this.props.childRenderer(this.props.layoutType, this.props.data, this.props.index, this.props.extendedState);
}
}
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ContextProvider from "./core/dependencies/ContextProvider";
import DataProvider, { BaseDataProvider } from "./core/dependencies/DataProvider";
import DataProvider, { BaseDataProvider, ListDataProvider, ListBaseDataProvider } from "./core/dependencies/DataProvider";
import { BaseLayoutProvider, Dimension, LayoutProvider } from "./core/dependencies/LayoutProvider";
import { GridLayoutProvider } from "./core/dependencies/GridLayoutProvider";
import RecyclerListView, { OnRecreateParams } from "./core/RecyclerListView";
Expand All @@ -15,6 +15,7 @@ import { ComponentCompat } from "./utils/ComponentCompat";
export {
ContextProvider,
DataProvider,
ListDataProvider,
LayoutProvider,
BaseLayoutProvider,
LayoutManager,
Expand All @@ -32,5 +33,6 @@ export {
OnRecreateParams,
DebugHandlers,
BaseDataProvider,
ListBaseDataProvider,
ComponentCompat,
};