new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Expo & React Native · all subjects

guides

835 notes in this subject, read out of this brain and free to use. This is page 4 of 14.

Local builds complement EAS Build

Building your app locally complements EAS Build. You can keep using the build service for cloud automation and fall back to local builds for development.

Android --variant flag for product flavors

The --variant flag can switch the Android build type from debug to release. This flag can also configure a product flavor and build type when formatted in camelCase. For example, if you have free and paid product flavors, you can run npx expo run:android --variant freeDebug or npx expo run:android --variant paidDebug to build a development version of your app.

Android --app-id flag for custom application IDs

The --app-id flag can be used with npx expo run:android to launch the app after building using a customized application ID. For example, if your product flavor free uses applicationIdSuffix .free or applicationId dev.expo.myapp.free, you can run npx expo run:android --variant freeDebug --app-id dev.expo.myapp.free.

Android product flavors with expo run:android

If you have a custom Android project with multiple product flavors using different application IDs, you can configure npx expo run:android to use the correct flavor and build type using the --variant and --app-id flags.

Custom Android build types not recommended

Customizing the Android build type is possible but would break Expo's assumption that the build type release is used for production. Using a different build type instead of release might build unoptimized code in your app.

Using prebuild --clean for consistent native generation

To avoid issues from layering changes, you can use the npx expo prebuild --clean command. This command deletes existing native directories before regenerating them, ensuring that the project is always managed consistently. Native directories are automatically added to the project's .gitignore when you create a new project.

Production build variants for local compilation

You can pass --variant release (Android) or --configuration Release (iOS) to npx expo run:android or npx expo run:ios to build a production build of your app locally. Note that these builds are not signed and you cannot submit them to app stores. To sign your production build, see the Local app production guide.

Android debugOptimized variant for faster development

Starting in SDK 54, you can pass the --variant debugOptimized flag to npx expo run:android for faster development iteration. This provides a faster feedback loop compared to standard debug builds.

Device selection flag for local builds

You can add the --device flag to npx expo run:android or npx expo run:ios to select a device to run the app on. You can select a physically connected device or emulator/simulator.

Metro bundler refresh workflow after first build

Once the app is compiled and installed on your device or emulator, you don't need to rebuild every time you make a change. If you're only modifying JavaScript or TypeScript code, you can run npx expo start to start the Metro bundler on its own. Then press A for Android or I for iOS in the terminal to launch the already-installed app. Metro serves your updated JavaScript bundle without recompiling native code, so the app loads in seconds instead of minutes.

npx expo run:android and npx expo run:ios commands

The commands npx expo run:android and npx expo run:ios compile your project locally using your locally installed Android SDK or Xcode into a debug build. Each command performs two steps: it compiles and installs the native binary on your device or emulator, then starts the Metro bundler to serve your JavaScript or TypeScript code.

When to use expo run vs expo start

Use npx expo run:android or npx expo run:ios for the first build, after adding a native library, or after modifying a config plugin. Use npx expo start for daily development when only changing JavaScript or TypeScript code.

Rebuilding after configuration changes

To modify your project's configuration or native code after the first build, you must rebuild your project using npx expo run:android or npx expo run:ios again. Running npx expo prebuild again layers the changes on top of existing files and may produce different results after the build.

Do not commit keystore file to version control

The keystore file (my-upload-key.keystore) and the gradle variables containing passwords in android/gradle.properties must not be committed to version control systems like Git. These contain sensitive credentials that should be kept private. Instead, store these variables in a ~/.gradle/gradle.properties file on your local computer.

Submit locally built Android .aab with EAS Submit

After creating a locally built .aab file, you can submit it to Google Play Console manually or use EAS Submit with the command `eas submit --platform android --path ./my-app.aab`. EAS Submit accepts locally built Android binaries.

Android gradle.properties signing configuration variables

In android/gradle.properties, add the following gradle variables for release builds: MYAPP_UPLOAD_STORE_FILE (path to keystore file), MYAPP_UPLOAD_KEY_ALIAS (key alias from credentials.json), MYAPP_UPLOAD_STORE_PASSWORD (keystore password), and MYAPP_UPLOAD_KEY_PASSWORD (key password from credentials.json). For security, add these variables to ~/.gradle/gradle.properties instead of committing them to version control.

Generate Android Application Bundle with Gradle

To generate a release build in .aab format, navigate to the android directory and run `./gradlew app:bundleRelease`. This command creates app-release.aab in the android/app/build/outputs/bundle/release directory.

iOS release build requires Xcode manual submission

To create an iOS release build locally, use Xcode which handles signing and uploading to App Store Connect. Follow the manual submission guide for configuring a release scheme, archiving your app with Xcode, and uploading it to App Store Connect.

Prerequisites for Android local release build

To create an Android release build locally, you need OpenJDK installed (to access the keytool command) and the android directory must be generated. If using Continuous Native Generation (CNG), run `npx expo prebuild` to generate the android directory.

Create Android release build with keytool command

To create an upload key for Android release builds, run the keytool command: `sudo keytool -genkey -v -keystore my-upload-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000`. This generates a keystore file that must be moved to the android/app directory. You will be prompted to enter a password to protect the upload key, which you must remember for later configuration steps.

Download credentials from EAS Build for local release build

If you have already created a build with EAS Build, you can reuse your credentials for a local release build. Run `eas credentials -p android` and select the build profile, then select credentials.json and download credentials from EAS to credentials.json. Move the downloaded keystore.jks file to android/app directory and copy the upload keystore password, key alias, and key password values from credentials.json for use in gradle configuration.

Local HTTPS development setup for Expo web

When developing Expo web apps locally, you can set up local HTTPS to test secure browser APIs. The setup involves using mkcert to create development certificates and local-ssl-proxy to forward HTTPS traffic from port 443 to the Expo dev server on port 8081.

Local HTTPS access URL

After setting up the HTTPS proxy and certificates, open https://localhost in your browser to access your Expo app running with HTTPS.

mkcert certificate generation for localhost

Run 'mkcert localhost' from your project's root directory to generate two signed certificate files: localhost.pem (certificate) and localhost-key.pem (private key). Before using mkcert, run 'mkcert -install' to install the local certificate authority (CA).

local-ssl-proxy configuration for Expo web HTTPS

Start the HTTPS proxy with: npx local-ssl-proxy --source 443 --target 8081 --cert localhost.pem --key localhost-key.pem. This creates a proxy that forwards HTTPS traffic from port 443 to your Expo dev server on port 8081.

Expo web dev server default port

The Expo development server for web runs on http://localhost:8081 by default when started with 'npx expo start --web'.

Benefits of local HTTPS development

Local HTTPS development provides: team scalability with same setup for everyone, authentication support for HTTP-Only Cookies and secure contexts, production parity to match production HTTPS environments, and easy sharing with consistent development URLs across the team.

useKeyboardHandler hook for smooth keyboard animation

Example of useKeyboardHandler hook to track keyboard height and animate views: import { useKeyboardHandler } from 'react-native-keyboard-controller'; import Animated, { useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; const useGradualAnimation = () => { const height = useSharedValue(0); useKeyboardHandler( { onMove: event => { 'worklet'; height.value = Math.max(event.height, 0); }, }, [] ); return { height }; }; This hook uses useSharedValue to track keyboard height across animation frames via the onMove callback.

KeyboardAwareScrollView with KeyboardToolbar form example

Example showing KeyboardAwareScrollView and KeyboardToolbar for handling multiple inputs: import { TextInput, View, StyleSheet } from 'react-native'; import { KeyboardAwareScrollView, KeyboardToolbar } from 'react-native-keyboard-controller'; export default function FormScreen() { return ( <> <KeyboardAwareScrollView bottomOffset={62} contentContainerStyle={styles.container}> <View> <TextInput placeholder="Type a message..." style={styles.textInput} /> <TextInput placeholder="Type a message..." style={styles.textInput} /> </View> <TextInput placeholder="Type a message..." style={styles.textInput} /> <View> <TextInput placeholder="Type a message..." style={styles.textInput} /> <TextInput placeholder="Type a message..." style={styles.textInput} /> <TextInput placeholder="Type a message..." style={styles.textInput} /> </View> <TextInput placeholder="Type a message..." style={styles.textInput} /> </KeyboardAwareScrollView> <KeyboardToolbar /> </> ); } const styles = StyleSheet.create({ container: { gap: 16, padding: 16, }, listStyle: { padding: 16, gap: 16, }, textInput: { width: 'auto', flexGrow: 1, flexShrink: 1, height: 45, borderWidth: 1, borderRadius: 8, borderColor: '#d8d8d8', backgroundColor: '#fff', padding: 8, marginBottom: 8, }, }); This example shows how to use KeyboardAwareScrollView with multiple grouped inputs and KeyboardToolbar for navigation.

Chat screen with keyboard height animation example

Example demonstrating animated view pushed by keyboard height in a chat screen: import { StyleSheet, Platform, FlatList, View, StatusBar, TextInput } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; import { useKeyboardHandler } from 'react-native-keyboard-controller'; import MessageItem from '@/components/MessageItem'; import { messages } from '@/messages'; const useGradualAnimation = () => { const height = useSharedValue(0); useKeyboardHandler( { onMove: event => { 'worklet'; height.value = Math.max(event.height, 0); }, }, [] ); return { height }; }; export default function ChatScreen() { const { height } = useGradualAnimation(); const fakeView = useAnimatedStyle(() => { return { height: Math.abs(height.value), }; }, []); return ( <View style={styles.container}> <FlatList data={messages} renderItem={({ item }) => <MessageItem message={item} />} keyExtractor={item => item.createdAt.toString()} contentContainerStyle={styles.listStyle} /> <TextInput placeholder="Type a message..." style={styles.textInput} /> <Animated.View style={fakeView} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0, }, listStyle: { padding: 16, gap: 16, }, textInput: { width: '95%', height: 45, borderWidth: 1, borderRadius: 8, borderColor: '#d8d8d8', backgroundColor: '#fff', padding: 8, alignSelf: 'center', marginBottom: 8, }, }); This example shows how to use keyboard height to animate a view that pushes content above the keyboard with smooth animation.

KeyboardAwareScrollView bottomOffset prop adjusts scroll space

The KeyboardAwareScrollView component accepts a bottomOffset prop to add extra space between the keyboard and content. In examples, this is commonly set to values like 62 to account for additional UI elements like toolbars.

useKeyboardHandler hook accesses keyboard lifecycle events

The useKeyboardHandler hook from react-native-keyboard-controller provides access to keyboard lifecycle events and allows determination of when the keyboard starts animating and its position in every frame of the animation. It accepts an object with callback functions like onMove that receives an event parameter containing the keyboard height.

KeyboardAvoidingView example with platform-specific behavior

Example of KeyboardAvoidingView component: import { KeyboardAvoidingView, TextInput } from 'react-native'; export default function HomeScreen() { return ( <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} style={{ flex: 1 }}> <TextInput placeholder="Type here..." /> </KeyboardAvoidingView>; ); } This example sets behavior to 'padding' on iOS and undefined on Android.

Keyboard.dismiss() programmatically dismisses the keyboard

The Keyboard.dismiss method from React Native dismisses the keyboard programmatically. This can be called in response to user actions, such as pressing a button, or based on other conditions in the app.

react-native-keyboard-controller requires development build and reanimated

The react-native-keyboard-controller library is not included in Expo Go and requires a development build. Additionally, it requires react-native-reanimated to be installed and set up correctly for proper functionality.

KeyboardProvider wraps app layout for keyboard-controller setup

To use the react-native-keyboard-controller library, wrap your app's root layout with the KeyboardProvider component. This provider initializes the keyboard controller functionality for the entire app.

Install react-native-keyboard-controller with expo install

To install react-native-keyboard-controller in an Expo project, run: npx expo install react-native-keyboard-controller

KeyboardAwareScrollView auto-scrolls to focused TextInput

The KeyboardAwareScrollView component from react-native-keyboard-controller automatically scrolls to a focused TextInput field and provides native-like performance. It is a more powerful alternative to KeyboardAvoidingView for screens with multiple input fields and is excellent for simple screens with only a few elements.

Bottom tabs pushed above keyboard on Android with KeyboardAvoidingView

When using a Bottom Tab navigator on Android, focusing on an input field within a KeyboardAvoidingView causes the bottom tabs to be pushed above the keyboard. To address this, add the softwareKeyboardLayoutMode property to the Android configuration in app.json and set it to 'pan', then restart the development server and reload the app.

tabBarHideOnKeyboard option hides tabs when keyboard opens

The tabBarHideOnKeyboard option is available on the Bottom Tab Navigator. When set to true, the tab bar will be hidden when the keyboard opens. This is configured via the screenOptions property on the Tabs component.

Keyboard module listens to keyboard show and hide events

The Keyboard module from React Native allows listening for keyboard events using the Keyboard.addListener method. This method accepts an event name (such as 'keyboardDidShow' or 'keyboardDidHide') and a callback function. The callback is invoked when the keyboard is shown or hidden, and should return an object with a remove() method to unsubscribe from the event listener.

KeyboardToolbar handles input navigation alongside KeyboardAwareScrollView

The KeyboardToolbar component from react-native-keyboard-controller is used alongside KeyboardAwareScrollView to handle input navigation and prevent the keyboard from covering the screen without custom configuration. It displays navigation controls and a dismiss button, and works without configuration but can be customized.

KeyboardAvoidingView behavior property differs between Android and iOS

KeyboardAvoidingView handles the behavior property differently on each platform. On iOS, the 'padding' behavior works best and automatically adjusts the view's height, position, or bottom padding based on keyboard height. On Android, just having the KeyboardAvoidingView prevents input covering, so the behavior is typically set to undefined. The optimal setting may vary per app, so testing different options is recommended.

Keyboard listener example with dismiss button

Example demonstrating Keyboard.addListener and Keyboard.dismiss: import { useEffect, useState } from 'react'; import { Keyboard, View, Button, TextInput } from 'react-native'; export default function HomeScreen() { const [isKeyboardVisible, setIsKeyboardVisible] = useState(false); useEffect(() => { const showSubscription = Keyboard.addListener('keyboardDidShow', handleKeyboardShow); const hideSubscription = Keyboard.addListener('keyboardDidHide', handleKeyboardHide); return () => { showSubscription.remove(); hideSubscription.remove(); }; }, []); const handleKeyboardShow = event => { setIsKeyboardVisible(true); }; const handleKeyboardHide = event => { setIsKeyboardVisible(false); }; return ( <View> {isKeyboardVisible && <Button title="Dismiss keyboard" onPress={Keyboard.dismiss} />} <TextInput placeholder="Type here..." /> </View> ); } This example toggles keyboard visibility state and shows a dismiss button only when the keyboard is active.

Instant for local-first Expo apps

Instant is a modern alternative to Firebase providing a real-time database. It allows developers to focus on building their app's frontend while Instant handles the database layer.

Local-first architecture definition

Local-first software allows users to work offline by reading and writing directly to a database on their device. Users can trust the software to work offline, and when connected to the internet, data is seamlessly synced and available on all devices running the app. This architecture enables both real-time collaboration (when online) and asynchronous syncing (when offline).

Turso with offline sync for Expo

Turso is a modern database service built on SQLite that supports Offline Sync for true local-first experiences. It enables syncing databases between local and remote sources with bidirectional sync and built-in conflict detection. Turso can be used with expo-sqlite. Automatic conflict resolution is not yet available.

RxDB for local-first apps

RxDB is a local-first, NoSQL database for JavaScript applications that is deeply reactive, allowing subscriptions to query results so the UI updates automatically when data changes. RxDB works with Expo using the SQLite storage adapter, which wraps expo-sqlite. It offers replication plugins to sync with existing backends including HTTP, GraphQL, Supabase, or custom implementations.

Jazz local-first database

Jazz is a local-first relational database with real-time sync, offline support, and row-level permissions. It is open source, provides first-class support for Expo and React Native, and can be self-hosted.

LiveStore for local-first Expo apps

LiveStore is a client-centric local-first data layer for high-performance applications with first-class support for Expo. It provides a SQLite-based data layer suitable for building local-first apps.

Local-first user experience benefits

Local-first software feels fast because interactions are not network-bound. Users can read and write directly from/to a local database on their device. Data syncs seamlessly when connected to the internet, and the software supports both real-time collaboration (like Figma) and asynchronous syncing (like Linear when creating tasks offline).

Other local-first tools to explore

Additional local-first tools worth exploring include Automerge, ElectricSQL, and PowerSync. A more comprehensive list is available on the Local-first software community website at localfirstweb.dev.

Yjs CRDT for local-first sync

Yjs is a CRDT implementation providing data types that can be synced across multiple clients. When building apps with Yjs, use Y.Array and Y.Map to represent syncable data instead of Array and Object. TinyBase can be used for state management on top of Yjs, and persistence can be handled by various tools from JSON files to databases like y-expo-sqlite.

Local-first development challenges

Local-first tools are still in early stages. Developers may need to implement custom sync layers or figure out how to handle permissions for multiple users operating on the same data. The ecosystem is still evolving, and adopting local-first tools requires being prepared as an early adopter.

Legend-State for local-first apps

Legend-State is a state and sync library supporting Expo and React Native via react-native-async-storage. Primary goals include faster state management for React apps, fine-grained reactivity for minimal renders, and powerful sync and persistence with built-in Supabase support. Get started using: npx create-expo-app --example with-legend-state-supabase

Local-first developer experience benefits

Developers no longer need to manage multiple app states for each network request (loaded, loading, error) and their corresponding UI states. Instead, write to a local database and the app automatically syncs changes to the server. This allows developers to focus on building the app rather than managing networking and offline states. Server outages no longer prevent users from accessing the app and continuing work.

TinyBase for local-first apps

TinyBase is a reactive data store for local-first apps and state management library that integrates with popular syncing and persistence layers like Yjs and SQLite. On Android and iOS, TinyBase uses expo-sqlite for data persistence. On the web, it relies on localStorage. TinyBase works seamlessly with Expo Go for quick development. Get started using: npx create-expo-app --example with-tinybase

Prisma for local-first Expo apps

Prisma is available for Expo and React Native in early access and aims to provide a complete local-first solution covering state management, syncing, and persistence. Prisma is well known as the most popular ORM for Node.js and TypeScript backends.

Expo SQLite for local-first persistence

Expo SQLite is a SQLite library for persistence in local-first apps. It can be used with different state management and syncing layers, such as y-expo-sqlite for persisting Yjs documents and TinyBase as a state management layer. Using SQLite is flexible but requires combining it with other tools or building custom tools to achieve a complete local-first solution.

Example: metro.config.js with Terser configuration

const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); config.transformer.minifierPath = 'metro-minify-terser'; config.transformer.minifierConfig = { // Terser options... }; module.exports = config;

Give your agent this brain