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

Users5 minutes read

Jake Lynch

Jake Lynch

Guest Author

Fig helps millions with dietary restrictions find safe food. Here's how a five-person team uses the Expo SDK and EAS Update to ship reliably and move fast.

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

Fig (Food is Good) helps people with any dietary restriction or allergy find food that they can actually eat — and avoid reactions that can seriously impact their health.

From scanning ingredients and discovering safe products to seeing how well restaurants accommodate specific needs, Fig makes eating easier and safer for anyone with complex dietary needs. Millions of people (including most of the Fig team!) rely on Fig to make safe, confident food choices.

Last year we were a finalist for an Expo App Award. The team at Expo appreciated the native feel and snappy functionality of our app so that’s what we’ll cover in this blog.

Why Expo?

Fig began as a standard React Native app but has increasingly adopted Expo to accelerate development and improve reliability.

We are a small team of five engineers, so we don’t have much time to waste with complexity. After getting burned a few times by community packages that would break and were no longer maintained, we decided to integrate the SDK over time. We have grown to trust the libraries and plan on integrating more in the future. When AppCenter was decommissioned by Microsoft last year, we also moved our over-the-air updates to Expo’s EAS system for critical javascript patches.

The Expo SDK has been critical to Fig’s success

Here are a few examples of Expo SDK packages that have helped Fig develop a performant app, quickly with a small team:

expo-image

expo-image enables Fig to efficiently render images and SVGs to make the app experience friendly to members of our community. Since the stock React Native Image component doesn’t support SVGs, we initially used react-native-svg for all of our SVG rendering needs, especially our icons. Passing the SVG string to SvgXml made setting attributes like stroke, stroke-width, and fill super easy.

Fig’s Settings screen with expo-image rendering SVGs

But after reading this blog post from Software Mansion, we realized creating component trees from SVGs comes with a cost, and is probably overkill for static icons. We moved our icons to expo-image so that native libraries can handle the SVG rendering, and we use react-native-svg only when we need to manipulate/animate the SVG internals.

Since expo-image’s ability to change the SVG is limited (tintColor sets the color of all the non-transparent pixels), this does mean that we bundle a couple more SVGs when we need to vary things like stroke-width, but believe the performance trade-off is worth it.

react-native-svg:

Code
// 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:

Code
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 uses expo-location to handle fetching location data for their restaurants search feature. We moved from @react-native-community/geolocation to get more granular control over location data from a more actively maintained library.

We fetch approximate coordinates and more accurate coordinates simultaneously. We utilize cached location data when it’s recent enough. This ensures we can populate the map view and restaurant search with nearby restaurants quickly. The location is stored in a context, and then used throughout the app, particularly for our new restaurant search feature.

Fig’s screen requesting a user’s location | A corresponding MapBox view once location has been enabled.

Code
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

Fig has been burned a few times by community packages breaking when platform updates deprecate APIs.

One example was when iOS 18 deprecated SKStoreReviewController and react-native-in-app-review stopped working. expo-store-review enabled Fig to quickly adopt a new library to prompt users for ratings and feedback that support all the operating system versions we needed.

Integrating the screen is simple and unlike many community libraries, Expo’s library continues to be maintained even as underlying APIs changed.

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

Pushing critical patches with Expo’s OTA Updates

After App Center retired CodePush in March of 2025, Fig adopted Expo EAS Updates to continue delivering critical fixes and experimental experience tests instantly. This allowed us to have confidence shipping code and to quickly address bugs that reached production. While we still use Bitrise for our production builds, we like how we can trust Expo’s OTA update system to deliver critical updates to our users when needed.

Fig’s future with Expo

Fig will continue to integrate more Expo modules to reduce maintenance burden, simplify build complexity, and unlock new native capabilities as the platform expands. React Native version upgrades were once a dreaded task that we avoided until things broke.

As we have migrated to more Expo packages, the upgrade process has become easier and more reliable, allowing us to stay on supported versions of React Native without having to waste too many development cycles.

OTA Updates
Expo SDK

Dive in, and create your first Expo project

Learn more