OpenAIExpoMar 31, 2026, 1:30 PM

How Fig keeps millions eating safely with a five-engineer team and Expo

A condensed section focused on the key takeaways first.

Original Post

Quick Digest

Summary

A condensed section focused on the key takeaways first.

openaienmodel: gpt-5-mini-2025-08-07

How Fig keeps millions eating safely with a five-engineer team and Expo

Key Points

  • Small team adopted Expo SDK and EAS for reliability
  • expo-image for native SVG rendering, react-native-svg for animations
  • Parallel cached location fetch + EAS OTA for critical patches

Summary

Fig (Food is Good) uses the Expo SDK and EAS Updates to run a high-quality mobile app with a five-engineer team. By incrementally adopting maintained Expo modules, Fig reduced maintenance burden, improved performance, and regained confidence to ship critical fixes via OTA updates after AppCenter/CodePush was retired.

Key Points

  • Adopt Expo SDK packages to reduce reliance on fragile community packages and simplify upgrades.
  • Use expo-image for static SVG icons (native rendering, tintColor support) and keep react-native-svg for cases that need DOM-like SVG manipulation or animation; accept bundling a few SVG variants as a performance trade-off.
  • Use expo-location with a cached/parallel fetch strategy: fetch approximate and high-accuracy coordinates concurrently, prefer cached last-known position when recent to speed UI population.
  • Replace deprecated/unmaintained libraries with maintained Expo alternatives (example: expo-store-review vs react-native-in-app-review) to avoid platform-deprecation breakage.
  • Adopt EAS Updates for over-the-air critical JS patches and experiments after AppCenter/CodePush retirement; continue using CI (e.g., Bitrise) for production builds while relying on EAS for rapid fixes.
  • Net result: fewer upgrade headaches, faster iteration with a tiny team, and reliable user-facing releases.

Practical takeaways for engineers

  • Audit fragile community deps and prefer maintained Expo modules when they meet requirements.
  • For SVGs: prefer native image rendering for static icons; reserve react-native-svg for animated/manipulated SVGs.
  • Implement location fetching with a max-age cache and parallel approximate + accurate requests for fast, accurate UX.
  • Use EAS Updates for emergency patches and staged experience tests while keeping reproducible CI builds for app-store binaries.

Full Translation

Translations

A translation section that keeps the flow of the original article.

openaijamodel: gpt-5-mini-2025-08-07

5人のエンジニアチームとExpoで、Figが何百万人の安全な食事を守る方法

Users • 2026-03-31 • 5分読了

執筆: Jake Lynch(ゲスト著者)、Taylor Harrison(ゲスト著者)

Fig(Food is Good)は、食事制限やアレルギーを持つ人々が実際に食べられる安全な食品を見つけ、重篤な反応を避けられるよう支援します。成分のスキャンや安全な製品の発見、レストランが特定のニーズにどの程度対応しているかの確認まで、Figは複雑な食事ニーズを持つ人々の外食や買い物をより簡単かつ安全にします。何百万人もの人々(Figチームのほとんども含む!)が安全で自信を持った食事選びにFigを頼っています。

昨年、私たちはExpo App Awardのファイナリストになりました。Expoチームはアプリのネイティブ感とキビキビした操作性を評価してくれました。本記事ではその点について詳しく紹介します。

なぜExpoなのか

Figは当初標準的なReact Nativeアプリとして始まりましたが、開発の加速と信頼性向上のために徐々にExpoを採用してきました。エンジニアは5人しかいないため、複雑さに時間を浪費する余裕がありません。コミュニティ製パッケージが壊れてメンテナンスされなくなり痛い目にあった経験から、SDKを段階的に取り込む決断をしました。ライブラリへの信頼が高まり、将来的にさらに統合する計画です。

また、MicrosoftがAppCenterを廃止した昨年、重要なJavaScriptパッチのOTA配信をExpoのEAS Updatesに移行しました。

Expo SDKはFigの成功に不可欠だった

ここでは、小規模チームで高速かつ高性能なアプリを開発するうえで役立ったExpo SDKパッケージのいくつかを紹介します。

expo-image

expo-imageは、Figが画像やSVGを効率的にレンダリングして、コミュニティのメンバーにとって使いやすいアプリ体験を提供するのに役立ちました。標準のReact NativeのImageコンポーネントはSVGをサポートしないため、当初は react-native-svg を使ってアイコンなどのSVG描画を行っていました。SVG文字列をSvgXmlに渡すと、strokeやstroke-width、fillの属性を簡単に設定できます。

しかしSoftware Mansionのブログを読んで、SVGからコンポーネントツリーを作ることにはコストが伴い、静的なアイコンには過剰であると判断しました。アイコンをexpo-imageに移行することでネイティブライブラリにSVGレンダリングを任せ、SVG内部を操作・アニメーションしたい場合だけreact-native-svgを使うようにしています。

expo-imageはSVGの変更能力が限定的(tintColorは非透明ピクセル全体の色を設定する)なため、stroke-widthなどを変えたい場合は追加のSVGをバンドルする必要がありますが、パフォーマンスのトレードオフは十分に価値があると考えています。

react-native-svg(例):

// Imports svg as a string
import thumbsUpIcon from '@assets/icons/thumbs-up.svg';
import { iconDefault } from '@utils/colors.util';
const Icon: FunctionComponent<IconProps> = ({ iconSize, color, rotation, transform, height, width, ...otherProps }) => {
  const parsedIconSize = !height && !width ? getIconSize(iconSize) : undefined;
  const sizeProps: { height?: NumberProp; width?: NumberProp } = {};
  if (parsedIconSize || height) {
    sizeProps.height = parsedIconSize ?? height;
  }
  if (parsedIconSize || width) {
    sizeProps.width = parsedIconSize ?? width;
  }
  // svg string is passed via `xml` in otherProps
  return (
    <SvgXml
      color={color ?? iconDefault}
      transform={rotation ? [{ rotate: `${rotation}deg` }] : transform}
      {...sizeProps}
      {...otherProps}
    />
  );
};

expo-image(例):

import { Image } from 'expo-image';
import styled, { css } from 'styled-components/native';
// Icons are `require`d from the assets folder, like other static assets
export const iconAssets = {
  thumbsUp: require('@assets/icons/thumbs-up.svg'),
  ...
} as const;
export const StyledImage = styled(Image)<{ width?: number; height?: number; rotation?: number }>`
  ${({ width }) => width !== undefined && css`width: ${width}px;`}
  ${({ height }) => height !== undefined && css`height: ${height}px;`}
  ${({ rotation }) => !!rotation && css`transform: rotate(${rotation}deg);`}
`;
const Icon: FunctionComponent<IconProps> = ({ iconSource: iconName, iconSize, height, width, rotation, color, ...otherProps }) => {
  const parsedIconSize = !height && !width ? getIconSize(iconSize) : undefined;
  // Icons are referenced by iconAssets key, native image libraries handle the rendering
  return (
    <StyledImage
      tintColor={color}
      source={typeof iconName === 'object' ? iconName : iconAssets[iconName]}
      width={parsedIconSize ?? width}
      height={parsedIconSize ?? height}
      rotation={rotation}
      contentFit="contain"
      {...otherProps}
    />
  );
};

expo-location

Figはレストラン検索機能で位置情報を扱うためにexpo-locationを使用しています。@react-native-community/geolocationから移行し、より積極的にメンテナンスされているライブラリで位置情報を細かく制御できるようになりました。

私たちは近似座標とより正確な座標を同時に取得し、キャッシュされた位置情報が十分新しければそれを利用します。これにより、地図ビューやレストラン検索を素早く近隣の店舗で埋められるようにしています。位置情報はコンテキストに保存され、アプリ全体(特に新しいレストラン検索機能)で利用します。

Figの位置情報許可を求める画面や、許可後のMapBoxビューがあります。

コード例:

import * as Location from 'expo-location';
// Max age of a cached location in milliseconds
const locationMaximumAge = 3 * 60 * 1000; // 3 minutes
const locationRequiredAccuracy = 11; // meters, just above the LocationAccuracy.High
const approximateLocationRequiredAccuracy = 3001; // meters, just above the LocationAccuracy.Lowest
export const LocationProvider: FunctionComponent<PropsWithChildren<LocationProviderProps>> = ({ children }) => {
  const [coordinates, setCoordinates] = useState<Coordinates>();
  const [approximateCoordinates, setApproximateCoordinates] = useState<Coordinates>();
  // You can alternatively use expo-location methods to request permission
  const { permissionStatus: locationPermissionStatus, triggerRequestPermission: triggerRequestLocationPermission, } = usePermission('location');
  const fetchCoordinates = useCallback(async (setNewCoords: (coords: Coordinates) => void, requiredAccuracy: number, locationAccuracy: Location.LocationAccuracy, maxAge?: number,) => {
    try {
      // Try to use a cached location first
      let location = await Location.getLastKnownPositionAsync({ requiredAccuracy, maxAge, });
      if (!location) {
        // If no cached location, get the current position, which may take some time
        location = await Location.getCurrentPositionAsync({ accuracy: locationAccuracy, });
      }
      const newCoordinates = location?.coords;
      setNewCoords(newCoordinates);
      return newCoordinates;
    } catch (e) {
      logFigError('Error getting location', { error: e, });
    }
  }, [mockLatitude, mockLongitude]);
  const updateLocation = useCallback(async () => {
    const [, newCoordinates] = await Promise.all([
      // Fetch approximate coordinates for faster loading
      fetchCoordinates(setApproximateCoordinates, approximateLocationRequiredAccuracy, Location.LocationAccuracy.Lowest),
      // Fetch more accurate coordinates for better accuracy
      fetchCoordinates(setCoordinates, locationRequiredAccuracy, Location.LocationAccuracy.High, locationMaximumAge),
    ]);
    return newCoordinates;
  }, [fetchCoordinates]);
  useEffectOnce(locationPermissionStatus === 'granted', () => { updateLocation(); });
  return (
    <LocationContext.Provider value={{ coordinates, approximateCoordinates, updateLocation }}>
      {children}
    </LocationContext.Provider>
  );
};

expo-store-review

プラットフォームのアップデートでAPIが非推奨になり、コミュニティパッケージが壊れることがありました。例えばiOS 18でSKStoreReviewControllerが非推奨になり、react-native-in-app-reviewが動作しなくなったことがありました。expo-store-reviewを使うことで、必要なOSバージョンをサポートするレビュー促進のライブラリに素早く移行できました。スクリーンへの組み込みもシンプルで、多くのコミュニティライブラリとは異なり、基盤となるAPIが変わってもExpoのライブラリは継続的にメンテナンスされています。

コード例:

import * as StoreReview from 'expo-store-review';
if (await StoreReview.hasAction()) {
  StoreReview.requestReview();
}

App Center廃止後のExpoのOTAアップデートによる重要パッチの配信

App Centerが2025年3月にCodePushを廃止した後、Figは重要な修正や実験的な体験テストを即座に配信するためにExpo EAS Updatesを採用しました。これによりコード出荷に自信を持てるようになり、本番に到達したバグに迅速に対処できるようになりました。生産ビルドは今もBitriseを使用していますが、必要に応じてExpoのOTA更新システムが重要なアップデートをユーザーに確実に届けてくれる点を高く評価しています。

Expoとともに歩むFigの未来

Figは今後もメンテナンス負荷を下げ、ビルドの複雑さを簡素化し、プラットフォームの拡張に伴う新しいネイティブ機能を活用するため、さらに多くのExpoモジュールを統合していきます。以前はReact Nativeのバージョンアップが恐怖のタスクで、壊れるまで避けていましたが、より多くのExpoパッケージへ移行したことでアップグレードプロセスは容易で信頼できるものになり、サポートされているReact Nativeのバージョンを保つために開発サイクルを無駄にしなくて済むようになりました。