前回、Firestoreからデータを取得してとりあえずログに表示するところと、3カラムレイアウトを組むところを試したので、今回は取り出したデータを3カラムレイアウトで画像表示してみます。

前回のソースコードで続きを書こうと思ったらなぜか動かなくなってしまった。経緯はメモし忘れたけど、とりあえずエラーメッセージは載っけておく。

Unable to resolve "@expo/vector-icons/Fontisto" from "node_modules/native-base/dist/src/basic/IconNB.js"
Error: Can't find react-native in package.json dependencies
Unable to resolve "react-native" from "node_modules/expo/build/launch/registerRootComponent.js"
Failed building JavaScript bundle.
Error: Can't find react-native in package.json dependencies

イマイチ解決の糸口が見つけられなかったので、一旦プロジェクトを作るところからやりなおすことに。。

$ rm -rf MenstagramRN
$ expo init MenstagramRN

前回は素直に従わずにnpmを使ったのですが、今回はexpoが推してくるyarnを使うことにします。

$ yarn start

なにやらシミュレータ上でエラーが出ていて動かない。シミュレータ内のExpoアプリを新しくしろとのこと。

The experience you requested requires a newer version of the Expo Client app.

下記エントリに記載されている通り、一度シミュレータのExpoアプリをアンインストールする。

https://www.aizulab.com/blog/project-not-shown-in-expo-client-app/

再びyarn startしたらちゃんとブランクのアプリが動いたので、次に進めます(スクショ取り忘れた)。

$ yarn start

ようやく本題に進める。まずはfirebaseのパッケージをインストール。

$ yarn add firebase

前回のコードと一緒だけど一応Firebaseのクライアントを初期化するコードを載っけとく。

firebase.js

import firebase from "firebase";
import 'firebase/firestore';
  
const firebaseConfig = {
  apiKey: "<apiKey>",
  authDomain: "<authDomain>",
  databaseURL: "<databaseURL>",
  projectId: "<projectId>",
  storageBucket: "<storageBucket>",
  messagingSenderId: "<messagingSenderId>",
  appId: "<appId>"
};

const firebaseApp = !firebase.apps.length
  ? firebase.initializeApp(firebaseConfig)
  : firebase.app()

const db = firebaseApp.firestore();

export default db

コンポーネントの実装。TypeScriptにしているけど、まだあんまりTypeScriptの使い方を理解していない。

ちなみに、前回は3カラムレイアウトを実装するのにreact-native-easy-gridを使ったけど、動的データを使って並べるのがやりにくそうだったのでFlatListでの実装に変えた。これはこれで、自分でImageのwidthを計算してるところとかスマートじゃないけど仕方ないのかな。

App.tsx

import React, { Component } from 'react';
import { StyleSheet, Text, View, Dimensions } from 'react-native';
import { Image, FlatList} from "react-native";
import { Container, Header } from 'native-base';
import db from './firebase'

export default class App extends Component {
  state = { ramens: [] };
  
  componentDidMount() {
    db.collection("ramens").get().then((querySnapshot) => {
      const ramens = querySnapshot.docs.map(doc => doc.data())
      this.setState({ ramens: ramens })
    });  
  }
  
  render() {
    return (
      <Container>
        <Header />
        <FlatList
          data={this.state.ramens}
          renderItem={({ item }) => (
            <View>
            <Image
              source={{ uri: item.imageUrl }}
              style={styles.imageStyle}
              />
            </View>
          )}
          numColumns={3}
          keyExtractor={(item, index) => index.toString()}
        />
      </Container>
    );
  }
}

const windowWidth = Dimensions.get('window').width;
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  imageStyle: {
    width: windowWidth / 3,
    height: windowWidth / 3,
    margin: 1,
    resizeMode: 'cover',
  }
});

よし出来た。

ラーメン一覧画面

Appの表記について

expo initで生成したときに出来るコードは以下のようになっているが、

export default function App() {

多くのサンプルコードを見ると、以下のようになっているケースのほうが多い。

export default class App extends Component {

完全に理解出来ているわけではないが、前者は関数コンポーネントで、後者はクラスコンポーネントということらしい。

https://ja.reactjs.org/docs/components-and-props.html

シンプルに書くなら関数コンポーネントでも良さそうだけど、stateを使ったりするコードはほとんどクラスコンポーネントなので、まあ普通はクラスコンポーネントの表記にしておくのが無難そう。