<file_sep># MovieBrowser

 <p align="center"> <img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/android/0.jpg" align="center" height="760" width="448" ></p>
 
## Motivation

MovieBrowser is a simple read-only app, made for Android and iOS, to get an fast overview of currently popular movies and TVShows. Therefore I made use of an API provided by TheMovieDB. Feel free to check it out, imho the best public movie db API out there.

## Implementation

For the implementation I used a cross-platform framework called React Native. The next section covers my entire development setup:

### Environment

As an editor I used **Visual Studio Code**.
The command `react-native info` gives me this information:

```
  React Native Environment Info:
    System:
      OS: macOS 10.14
      CPU: x64 Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz
      Memory: 1.67 GB / 16.00 GB
      Shell: 3.2.57 - /bin/bash
    Binaries:
      Node: 9.8.0 - ~/.nvm/versions/node/v10.9.0/bin/node
      Yarn: 1.3.2 - ~/.yarn/bin/yarn
      npm: 6.2.0 - ~/.nvm/versions/node/v10.9.0/bin/npm
      Watchman: 4.7.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0
      Android SDK:
        Build Tools: 23.0.1, 23.0.2, 25.0.0, 25.0.1, 25.0.2, 25.0.3, 26.0.1, 26.0.2, 26.0.3, 27.0.2, 27.0.3, 28.0.0
        API Levels: 16, 21, 23, 25, 26, 27, 28
    IDEs:
      Android Studio: 3.1 AI-173.4819257
      Xcode: 10.0/10A255 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.5.0 => 16.5.0
      react-native: 0.57.0 => 0.57.0
```

As you can see I used React Native version `0.57.0`.

For desiging UI elements I usually use `Affinity Designer`.

### Node packages

If you want to have a closer look at the specific versions of the packages, please have a look at the `package.json` (and maybe `package-lock.json`).

This is the list of frameworks that make it happen to realize this project in such short amount of time:

- **Axios**: Easy network request handling
- **Lodash**: Utility framework
- **Shoutem UI**: UI Framework
- **React Navigation**: Javascript based navigation framework
- **Eslint & eslint airbnb config**: Lint javascript code by using the airbnb config as a base.

### Features

The app uses API calls to `Discover Movie`, `Discover TVShows` and `Discover Genre`, where genre is a list of all available movie genres of TMDb. A search for custom input is implemented, which responds movie and tvshow results for a certain keyword. If you are interested in an item, you can tap on the cover and get transitioned to a detail screen where more detailed information are provided.

### Evaluation and Findings

As I'm coming from "native" iOS development, I'm always curious about a comparison between a cross-platform solution and a native one. This comparison usually includes time-to-implement, performance, look-and-feel and fun. Before you read my 2 cents of this you should barely know what my background and level of experience is: I've done various professional iOS projects in the past 2 years. I've gained a basic knowledge in Android development by university and spare time projects. Currently I focus on react-native. I'm heavily using it for over a year now at work and whenever I'm free. I became a fan of javascript and the react component pattern, even if it is against the SOC pattern I learned at university.
That should be enough for now, let's talk about the project:

#### Time-To-Implement

All togheter it took me a full-time week to setup up the project, search for an api, think of a simple design, implement it, write the readme and create the store screenshots. In my opinion that's fair as far as I get an Android and iOS app. If you're really experinced in Android and iOS development, you might be faster with building the UI and doing the setup, but doing network requests feels so easy with javascript and I'm sure this would take me/you a lot longer in Obj-C or Java. In my opinion for such small projects and with these requirements, react-native is conciderable alternative to native development.

#### Performance

React-native is using a bridge to call native elements on every platform. That means a JS-Thread is running while the app is active and interprets the javascript code. That takes some time and surely reduces the speed of the app. It's a bit a trap that you can use react-native for "easily" building native apps that perform like native apps. It is highly recommened to think about what you are doing in every component, for example are you holding references or recreate the same object every render cycle? Do you use `React.Components` or `React.PureComponents`? What's the difference? Why and when do I implement `shouldComponentUpdate`? Those thoughs make the difference between a beginner and an experienced one (Not only that, but I think you get what I'm trying to say). If we now have look at the MovieBrowser, I did everything I know to boost the performance. For example, I used Flatlists, only pass references and use PureComponents. Still, there is a delay for scrolling events on the main page in Android. IOS is fine. Why is this? The reason for that is the implementation of `Flatlist` which is a javascript-based implementation of a Scrollview on Android and iOS. If you ever got in touch with Android development you will know that there is a special way of implementation for lists called `RecyclerView`. If you want the take benefits of it you should either implement it natively and write your own bridge or use an existing one, both ways cause more work.

You always have to balance the input you want to give and the output you can expect and in this case it wouldn't pay off to think of performance improvements in that manner. To sum it up, the performance highly depends on your coding style and experience, but in the end, native makes a difference as it comes to speed and look and feel. Wow, what a nice transition to the next section ;-)

#### Look-And-Feel

There exists a myth that cross-platform feels like hell to use and everybody notices it. That's true....NOT! Same as in the last section you just have to put more effort in development. That means more separate designs for Android and iOS, separate navigation and separate components. Do you want to do that? Probably not. That would ruin you time calculation and the main benefit of cross-platform, wouldn't it? Therefore you should decide wisely. In this project I didn't care that much if it feels "like native" to save time, so I didn't customized the navigation or added a floating button for Android. But I could. That's the point. Coming from web development, bungle some tutorials and bloaded modules togheter and then complain about Look-And-Feel is just lazy and nothing more. You should start thinking differently if it comes to react-native, through away the opinions of unexperienced and lazy people and make your own experiences.

#### Fun

The reason why I'm doing this in general is fun. I enjoy coding in javascript (especially in es6/7) and like the react component pattern. Another benefit is that I can support both, Android and iOS. I could also only support one of them, it's up to me. As it comes to network request or basic object/array modifications, javascript (/ node) gives you tons of frameworks and toolkits. Network requests actually make fun! Can you imagine that? Great, I know :D. Even if I think from time to time "man, it would be great to do this in a storyboard", but the nice thing is, I could. To sum it up, it's fun to code in the react pattern with javascript.

### Drawbacks

As you can see in the `packages.json` I used a UI Framework/Collection to improve my development speed. For the MovieBrowser I chose `Shoutem`. `Shoutem` promotes that they can help with almost everywhere concering mobile app development. I was only interested in the UI part which let you build react-native app "in not time", as they say on their website. If you ask me, do it by yourself. For prototyping it's fine, you get a nice looking app in short time. But then you need to customize. You need to go through the documentation and you recognize, the style is separated from the view. That remembers me of html and css separation. I'm not sure what I should think about it. For me at least, it's strange, I just don't like it. I'm sure a lot of you might like those frameworks (shoutem, native base ...), but in my opinion it's another way for lazy people build nice looking apps really fast. In the end you have no idea what's happening.

Another issue that hunts me since my first moment with react-native is the framework itself. It's still not `1.0`, so breaking changes are possible with each minor version. You often run into problems which require an update of the react-native version. Sometimes it is working, but more often the projects are broken. I hate it, spend time to fix something that was already working and now broken because I wanted to fix something else. It's a bit exhausting. And the fact that it's never gonna stop because Android and iOS is constantly improving and updating, makes me feel a bit irated. Enough rant for now :D

### Conclusion

I implemented a movie browser which gets the information from TMDB with a cross-platform framework called react-native. I enjoy coding in javascript to get native apps and the massive amount of node packages which can be used. Cross-platform has its benefits and drawbacks, but it highly depends on the usecase. My giveaways are the following: Read the react-native docs precisely, write your own code, use less than more modules/frameworks and go through the whole project in mind beforehand to decide if native or cross-platform is the one to go for.

### Screenshots

To get a overview of the app and how it works I did some fancy store-like screenshots:

#### iOS

<table>
  <tr>
    <td><a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/ios/0.jpg" align="center" height="760" width="448" ></a></td>
    <td>
    <a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/ios/1.jpg" align="center" height="760" width="448" ></a>
    </td>
  </tr>
    <tr>
    <td><a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/ios/2.jpg" align="center" height="760" width="448" ></a></td>
    <td>
    <a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/ios/3.jpg" align="center" height="760" width="448" ></a>
    </td>
  </tr>
  
  </table>

#### Android

<table>
  <tr>
    <td><a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/android/0.jpg" align="center" height="760" width="448" ></a></td>
    <td>
    <a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/android/1.jpg" align="center" height="760" width="448" ></a>
    </td>
  </tr>
    <tr>
    <td><a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/android/2.jpg" align="center" height="760" width="448" ></a></td>
    <td>
    <a href="url"><img src="https://github.com/papsti7/MovieBrowserApp/blob/bb1428ee3189b79284b6b6043fe44919e2cd3cbf/Screenshots/android/3.jpg" align="center" height="760" width="448" ></a>
    </td>
  </tr>
    
  </table>
<file_sep>import axios from "axios";
import _ from "lodash";
import { AsyncStorage } from "react-native";
import * as Constants from "./Constants";
import ApiKey from "./ApiKey";

export const GetMovieById = async id => {
  const movieResponse = await axios.get(
    Constants.ROOT_URL.API +
      Constants.API_ENDPOINT.MOVIE +
      "/" +
      id +
      "?" +
      ApiKey
  );
  return movieResponse;
};

export const GetTVShowById = async id => {
  const showResponse = await axios.get(
    Constants.ROOT_URL.API +
      Constants.API_ENDPOINT.TVSHOW +
      "/" +
      id +
      "?" +
      ApiKey
  );
  return showResponse;
};

export const DiscoverMoviesByPopularity = async (page = 1, genre) => {
  let url =
    Constants.ROOT_URL.API +
    Constants.API_ENDPOINT.DISCOVER +
    Constants.API_ENDPOINT.MOVIE +
    "?" +
    ApiKey +
    "&" +
    "page=" +
    page;
  if (genre) {
    url += "&" + "with_genres=" + genre;
  }
  const movieResponse = await axios.get(url);
  return movieResponse;
};

export const DiscoverShowsByPopularity = async (page = 1, genre) => {
  let url =
    Constants.ROOT_URL.API +
    Constants.API_ENDPOINT.DISCOVER +
    Constants.API_ENDPOINT.TVSHOW +
    "?" +
    ApiKey +
    "&" +
    "page=" +
    page;
  if (genre) {
    url += "&" + "with_genres=" + genre;
  }
  const showResponse = await axios.get(url);
  return showResponse;
};

export const GetImageByPath = async path => {
  return await axios.get(Constants.ROOT_URL.IMAGE + path);
};

export const GetGenreList = async isMovie => {
  let genreResponse;
  if (isMovie) {
    genreResponse = await axios.get(
      Constants.ROOT_URL.API +
        Constants.API_ENDPOINT.GENRE_MOVIE_LIST +
        "?" +
        ApiKey
    );
  } else {
    genreResponse = await axios.get(
      Constants.ROOT_URL.API +
        Constants.API_ENDPOINT.GENRE_TVSHOW_LIST +
        "?" +
        ApiKey
    );
  }
  return genreResponse;
};

export const GetGenreNameById = async (id, isMovie = true) => {
  try {
    let genres;
    if (isMovie) {
      genres = JSON.parse(
        await AsyncStorage.getItem(Constants.ASYNCSTORAGE_KEYS.GENRE_MOVIE_LIST)
      );
    } else {
      genres = JSON.parse(
        await AsyncStorage.getItem(
          Constants.ASYNCSTORAGE_KEYS.GENRE_TVSHOW_LIST
        )
      );
    }
    if (genres) {
      const result = _.find(genres, genre => genre.id === id);
      if (result) {
        return result.name;
      } else {
        throw new Error("id not found in records");
      }
    } else {
      throw new Error("no records saved yet");
    }
  } catch (err) {
    try {
      const {
        status,
        data: { genres }
      } = await GetGenreList(isMovie);
      if (status === 200) {
        if (isMovie) {
          await AsyncStorage.setItem(
            Constants.ASYNCSTORAGE_KEYS.GENRE_MOVIE_LIST,
            JSON.stringify(genres)
          );
        } else {
          await AsyncStorage.setItem(
            Constants.ASYNCSTORAGE_KEYS.GENRE_TVSHOW_LIST,
            JSON.stringify(genres)
          );
        }
        const result = _.find(genres, genre => genre.id === id);
        if (result) {
          return result.name;
        } else {
          throw new Error("id not found in newly fetched records");
        }
      }
    } catch (err) {
      return "unknown genre";
    }
  }
};

export const SearchForMovieByName = async (query = "Flash") => {
  let url =
    Constants.ROOT_URL.API +
    Constants.API_ENDPOINT.SEARCH +
    Constants.API_ENDPOINT.MOVIE +
    "?" +
    ApiKey;

  url += "&" + "query=" + query;
  const searchResponse = await axios.get(url);
  return searchResponse;
};

export const SearchForShowByName = async (query = "Flash") => {
  let url =
    Constants.ROOT_URL.API +
    Constants.API_ENDPOINT.SEARCH +
    Constants.API_ENDPOINT.TVSHOW +
    "?" +
    ApiKey;

  url += "&" + "query=" + query;
  const searchResponse = await axios.get(url);
  return searchResponse;
};
<file_sep>import { createStackNavigator } from "react-navigation";
import React, { Component } from "react";
import MainScreen from "./screens/MainScreen";
import DetailScreen from "./screens/DetailScreen";
import SearchScreen from "./screens/SearchScreen";

class Router extends Component {
  render() {
    const StackNavigator = createStackNavigator(
      {
        main: {
          screen: MainScreen
        },
        detail: {
          screen: DetailScreen
        },
        search: {
          screen: SearchScreen
        }
      },
      {}
    );
    return <StackNavigator />;
  }
}

export default Router;
<file_sep>import React, { Component } from "react";
import { ScrollView } from "react-native";
import { ImageBackground, Tile, Title, Text } from "@shoutem/ui";
import { ROOT_URL, TYPE_MOVIE_DISCOVER } from "../Constants";
import { GetGenreNameById } from "../ApiUtil";

class DetailScreen extends Component {
  static navigationOptions = ({ navigation }) => {
    const { state } = navigation;
    const { item, type } = state.params;

    return {
      title:
        type === TYPE_MOVIE_DISCOVER ? item.original_title : item.original_name
    };
  };

  state = {
    genres: [],
    item: this.props.navigation.getParam("item", {}),
    type: this.props.navigation.getParam("type", TYPE_MOVIE_DISCOVER)
  };

  async componentDidMount() {
    const {
      item: { genre_ids },
      type
    } = this.state;
    const isMovie = type === TYPE_MOVIE_DISCOVER;
    try {
      let genres = await Promise.all(
        genre_ids.map(id => GetGenreNameById(id, isMovie))
      );
      genres = genres.map(genre => <Text key={genre}>{genre}, </Text>);
      this.setState({ genres });
    } catch (err) {
      console.log(err);
    }
  }
  render() {
    const { item, type, genres } = this.state;
    return (
      <ScrollView style={{ flex: 1 }}>
        <ImageBackground
          styleName="large-portrait"
          source={{
            uri: ROOT_URL.IMAGE + item.poster_path
          }}
        >
          <Tile>
            <Title styleName="md-gutter-top">
              {type === TYPE_MOVIE_DISCOVER ? item.title : item.name}
            </Title>
          </Tile>
        </ImageBackground>
        <Text>
          <Text styleName="bold">Release Date: </Text>
          {type === TYPE_MOVIE_DISCOVER
            ? item.release_date
            : item.first_air_date}
        </Text>
        <Text>
          <Text styleName="bold">Genres: </Text>
          {genres}
        </Text>
        <Title>
          <Title styleName="bold">Overview: </Title>
          {item.overview}
        </Title>
      </ScrollView>
    );
  }
}

export default DetailScreen;
<file_sep>import React, { PureComponent } from "react";
import _ from "lodash";
import { View, TouchableOpacity, FlatList, Image } from "react-native";
import { Title, Spinner } from "@shoutem/ui";
import { KEY_EXTRACTOR } from "../Constants";
import PlusSign from "../../assets/plus.png";
import CoverItem from "../components/CoverItem";

class HorizontalList extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      ...props,
      renderItem: ({ item }) => this.renderRowItem(item, props.type),
      actualApiCallPage: 1,
      isFetchingData: false
    };
  }

  componentDidMount() {
    this.onListRefresh();
  }

  onListRefresh = () => {
    new Promise(async (resolve, reject) => {
      const { apiCall, args } = this.state;
      try {
        const { status, data } = await apiCall(1, args);
        if (status === 200) {
          const { results } = data;
          this.listRef.scrollToOffset({ animated: true, offset: 0 });
          this.setState({ data: results, actualApiCallPage: 1 });
          resolve();
        } else {
          reject(status);
        }
      } catch (err) {
        reject(err);
      }
    });
  };

  fetchNewData = async () => {
    this.setState({ isFetchingData: true });
    const { apiCall, args, actualApiCallPage } = this.state;
    try {
      const newActualApiCallPage = actualApiCallPage + 1;
      const { status, data } = await apiCall(newActualApiCallPage, args);
      if (status === 200) {
        const { results } = data;
        this.setState({
          data: _.concat(this.state.data, results),
          actualApiCallPage: newActualApiCallPage
        });
      }
    } catch (err) {
      console.log(err);
    } finally {
      this.setState({ isFetchingData: false });
    }
  };

  onEndReached = async () => {
    await this.fetchNewData();
  };

  renderRowItem = (item, type) => {
    return (
      <CoverItem item={item} type={type} navigation={this.props.navigation} />
    );
  };

  renderFooterItem = () => {
    const { isFetchingData } = this.state;
    return (
      <View style={styles.footerItemViewStyle}>
        {isFetchingData ? (
          <Spinner style={styles.footerItemSpinnerStyle} />
        ) : (
          <TouchableOpacity onPress={this.fetchNewData}>
            <Image style={styles.footerItemImageStyle} source={PlusSign} />
          </TouchableOpacity>
        )}
      </View>
    );
  };

  getItemLayout = (data, index) => {
    return { length: 180, offset: (180 + 10) * index, index: index };
  };

  renderItemSeparator = () => <View style={{ width: 10, height: 250 }} />;

  getRef = ref => (this.listRef = ref);
  render() {
    const { title, data, renderItem } = this.state;
    return (
      <View style={styles.viewStyle}>
        <Title style={styles.titleStyle}>{title}</Title>
        <FlatList
          style={styles.listStyle}
          contentContainerStyle={styles.listContainerStyle}
          ItemSeparatorComponent={this.renderItemSeparator}
          ListFooterComponent={this.renderFooterItem}
          data={data}
          renderItem={renderItem}
          horizontal
          ref={this.getRef}
          showsHorizontalScrollIndicator={false}
          keyExtractor={KEY_EXTRACTOR}
          onEndReached={this.onEndReached}
          getItemLayout={this.getItemLayout}
        />
      </View>
    );
  }
}

const styles = {
  listStyle: { height: 250 },
  listContainerStyle: { paddingHorizontal: 20 },
  viewStyle: { height: 300 },
  titleStyle: {
    color: "white",
    paddingLeft: 20,
    paddingVertical: 10
  },
  footerItemViewStyle: {
    width: 180,
    height: 250,
    alignItems: "center",
    justifyContent: "center"
  },
  footerItemSpinnerStyle: {
    size: "large"
  },
  footerItemImageStyle: { width: 180, height: 250, resizeMode: "center" }
};
export default HorizontalList;
<file_sep>import React, { Component } from "react";
import _ from "lodash";
import {
  View,
  TouchableOpacity,
  FlatList,
  StatusBar,
  Platform
} from "react-native";

import { Heading, Icon } from "@shoutem/ui";
import DiscoverMovies from "../DiscoverMovies.json";
import DiscoverTVShows from "../DiscoverTV.json";
import {
  TYPE_MOVIE_DISCOVER,
  TYPE_SHOW_DISCOVER,
  TYPE_GENRE_MOVIE,
  COLOR,
  KEY_EXTRACTOR,
  CAPITALIZE_FIRST_LETTER
} from "../Constants";
import * as API from "../ApiUtil";
import HorizontalList from "../components/HorizontalList";

class MainScreen extends Component {
  static navigationOptions = ({ navigation }) => {
    return {
      headerTitle:
        Platform.OS === "android" ? (
          <Heading
            style={{ color: "white", paddingLeft: 20, paddingVertical: 10 }}
          >
            MovieBrowser
          </Heading>
        ) : (
          <Heading style={{ color: "white" }}>MovieBrowser</Heading>
        ),
      headerTintColor: "white",
      headerStyle: {
        backgroundColor: COLOR.NAVBAR
      },
      headerRight: (
        <TouchableOpacity
          style={{
            paddingVertical: Platform.OS === "android" ? 20 : 0,
            paddingHorizontal: 20
          }}
          onPress={() => navigation.navigate("search")}
        >
          <Icon name="search" style={{ color: "white" }} />
        </TouchableOpacity>
      )
    };
  };

  state = {
    isRefreshing: false,
    listData: []
  };

  horizontalListRefs = {};

  componentDidMount = async () => {
    const listData = [
      {
        type: TYPE_MOVIE_DISCOVER,
        title: "Discover Popular Movies",
        getRef: ref => {
          if (!this.horizontalListRefs[TYPE_MOVIE_DISCOVER] && ref) {
            this.horizontalListRefs[TYPE_MOVIE_DISCOVER] = ref;
          }
        },
        apiCall: API.DiscoverMoviesByPopularity,
        data: DiscoverMovies.results
      },
      {
        type: TYPE_SHOW_DISCOVER,
        title: "Discover Popular TVShows",
        getRef: ref => {
          if (!this.horizontalListRefs[TYPE_SHOW_DISCOVER] && ref) {
            this.horizontalListRefs[TYPE_SHOW_DISCOVER] = ref;
          }
        },
        apiCall: API.DiscoverShowsByPopularity,
        data: DiscoverTVShows.results
      }
    ];
    try {
      const {
        status,
        data: { genres }
      } = await API.GetGenreList(true);
      if (status === 200) {
        _.forEach(genres, genre => {
          const entry = {
            type: TYPE_GENRE_MOVIE,
            title: "Movie Genre " + CAPITALIZE_FIRST_LETTER(genre.name),
            args: genre.id,
            getRef: ref => {
              if (
                !this.horizontalListRefs[TYPE_GENRE_MOVIE + genre.id] &&
                ref
              ) {
                this.horizontalListRefs[TYPE_GENRE_MOVIE + genre.id] = ref;
              }
            },
            apiCall: API.DiscoverMoviesByPopularity,
            data: []
          };
          listData.push(entry);
        });
      }
    } catch (err) {
      console.log(err);
    }
    this.setState({ listData });
  };

  renderHorizontalList = item => {
    return (
      <HorizontalList
        {...item}
        ref={item.getRef}
        navigation={this.props.navigation}
      />
    );
  };

  renderListItem = ({ item }) => {
    return this.renderHorizontalList(item);
  };

  onRefresh = async () => {
    this.setState({ isRefreshing: true });
    try {
      const refreshListPromises = [];
      _.forOwn(this.horizontalListRefs, function(value, key) {
        refreshListPromises.push(value.onListRefresh());
      });
      await Promise.all(refreshListPromises);
    } catch (err) {
      console.log(err);
    } finally {
      this.setState({ isRefreshing: false });
    }
  };

  getItemLayout = (data, index) => {
    return { length: 300, offset: 300 * index, index: index };
  };

  getRef = ref => (this.overallListRef = ref);
  render() {
    const { listData } = this.state;
    return (
      <View style={{ flex: 1, backgroundColor: COLOR.BACKGROUND }}>
        <StatusBar backgroundColor="#111" />
        <FlatList
          data={listData}
          renderItem={this.renderListItem}
          showsVerticalScrollIndicator={false}
          keyExtractor={KEY_EXTRACTOR}
          onRefresh={this.onRefresh}
          ref={this.getRef}
          refreshing={this.state.isRefreshing}
          initialNumToRender={2}
          getItemLayout={this.getItemLayout}
        />
      </View>
    );
  }
}

export default MainScreen;
