Sep 12, 2023 by
Steven Songqi Pu
You have a new notification! You can now see the latest updates from your personal and organization accounts in the new website notification center.
You can visit the notification center by clicking the bell icon in the navigation bar. Currently only build limit threshold notifications are supported but more types of notifications will be added in the future.
Aug 25, 2023 by
Tomasz Czajecki
Do you love experimenting but want to have your main projects always at hand? Now you can pin them to the top of the list so they are always there when you need them!
To get started, visit your projects list, click on the three vertical dots icon and select "Pin project" option. You can also unpin the already pinned project via this actions menu.
Aug 22, 2023 by
Chris Walter
In an effort to make it easier to develop and grow your apps, we're announcing a new suite of analytics tools called EAS Insights. Our goal with EAS Insights is to provide developers with the information they need to better understand their users, assess their app's performance, and feel confident in their direction.
As of today, we've begun introducing project-level Insights for all Expo developers. These features offer a preview of what we're working on, and we will continue adding new features and functionality over time.
In order to use EAS Insights, simply add the expo-insights
module to your project. From there, we'll gather some basic data and offer breakdowns on the /insights
page of your project on expo.dev.
If you have questions about EAS Insights or any feedback about these preview features, let us know.
Aug 10, 2023 by
Szymon Dziedzic
It’s extremely satisfying when you eas build
on a new project and it Just Works — at Expo, we know what we need to do to build Android and iOS React Native apps, and we handle all of that automatically for you. In many cases, this is all a project ever needs; but, not always.
We’ve come across a number of cases where developers have asked to replace single steps in their build process with their own implementations (such as for installing dependencies for Rush or Nx monorepos) or even replace the majority of the build process in order to build for another platform (such as Electron app or one of the many TV platforms). We’ve also heard from developers that want to use EAS Build to run their test suite, so that they could consolidate their CI into a single service.
To enable all of these use cases and more, today we are releasing a preview of our new fully customizable build process. You can use it by creating a yml
file in .eas/build
and pointing to that file from the config
field on your build profile. For example, you can configure a build profile to run tests with the following:
eas.json
{
"build": {
"test": {
"config": "test.yml",
"withoutCredentials": true
},
// other build profiles...
}
}
.eas/build/test.yml
build:
name: Run tests
steps:
- eas/checkout
- eas/install_node_modules
- run:
name: Unit tests
command: |
echo "Running tests..."
npm test
When you run eas build -p android --profile test
, you'll see the following:
If you needed to rewrite the entire build process from scratch as soon as you wanted to replace a single step, this wouldn’t be particularly useful. Each step in the standard build process can be used in your custom build definition. In the above test.yml example, we are using eas/checkout
to check out the repository and eas/install_node_modules
to re-use the same logic for installing Node modules (select the correct package manager, handle monorepos appropriately, etc).
For example, the following steps will create a development build for Android:
build:
name: Development build - Android
steps:
- eas/checkout
- eas/use_npm_token
- eas/install_node_modules
- eas/prebuild
- eas/run_gradle
- eas/find_and_upload_build_artifacts
And the equivalent for iOS:
build:
name: Development build - iOS
steps:
- eas/checkout
- eas/use_npm_token
- eas/install_node_modules
- eas/prebuild
- run:
name: Install pods
working_directory: ./ios
command: pod install
- eas/generate_gymfile_from_template
- eas/run_fastlane
- eas/find_and_upload_build_artifacts
Example configurations for store-ready builds are available in the eas-custom-builds-example repository: Android, iOS.
You can learn more about the provided reusable steps and others in the “Built-in EAS functions” documentation. You can also fork these steps in your own JavaScript / TypeScript functions, or write your own build steps from scratch.
If you’d like to fork a default build step, or if you just want to use JavaScript or TypeScript instead of Bash because the logic for a step is complex, you can run npx create-eas-build-function@latest .eas/build/my-new-function
to create a new function (we suggest creating the function in the .eas/build
directory, next to your YAML configuration — but you can put the functions anywhere in your repository).
After you’ve defined your function, you can refer to it from your build configuration YAML file. Refer to the README.md in the generated directory for more information about how to use it. Your configuration will look something like this:
eas.json
{
"build": {
"test": {
"config": "test.yml",
"withoutCredentials": true
},
// other build profiles...
}
}
.eas/build/test.yml
build:
name: Run tests
steps:
- eas/checkout
- eas/install_node_modules
- run:
name: Unit tests
command: npm run test
- my-new-function
functions:
my-new-function:
name: my-new-function
path: ./my-new-function
.eas/build/my-new-function/src/index.ts
import { BuildStepContext } from '@expo/steps';
async function myFunction(ctx: BuildStepContext): Promise<void> {
ctx.logger.info('Hello from my TypeScript function!');
}
export default myFunction;
Learn more about creating EAS Build functions with TypeScript.
eas/configure-eas-update-if-installed
step only supports EAS Update. Example of configuring EAS Update.This new feature helps bring EAS Build more in alignment with the spirit of how we think about building apps at Expo — our tools and services give you great defaults out of the box, and when you need to, you can opt-out of any those default choices and customize any part of your experience without having to face a sudden complexity cliff. Keep what works for you, change what doesn’t.
Some ideas for areas we’d like feedback on:
eas/
functions. Are they the right level of abstraction? Should they accept additional inputs for further customization, or provide additional outputs for use in subsequent steps?Feel free to send us feedback on Discord, @expo, Threads, or Bluesky.
A special mention goes out to Dominik Sokal, who built out the foundation of custom builds.
Aug 10, 2023 by
Alan Hughes
We're releasing an alpha version of the expo-sqlite
library that includes a proof-of-concept iOS integration with CR-SQLite, an SQLite extension that "allows merging different SQLite databases together that have taken independent writes" that makes up part of the Vulcan toolchain.
We're also releasing an example project that uses CR-SQLite with TinyBase, a reactive datastore for local first apps. It demonstrates what is known as local-first architecture. For a comprehensive look at this approach and why we think it matters, check out Johannes Schickling's "Local-first app development" talk at App.js 2023, the Ink & Switch "Local-first software" essay that coined the term, and the local-first community at LFW.dev.
CR-SQLite is a run-time loadable extension for SQLite and libSQL. It allows merging different SQLite databases together that have taken independent writes.
With CR-SQLite, your users can all write to their own app's local SQLite database while offline. They can then come online and merge their databases together, without conflict.
In other (much more technical) words, CR-SQLite adds multi-master replication and partition tolerance to SQLite via conflict-free replicated data types (CRDTs) and/or causally ordered event logs.
For more information, check out the CR-SQLite README.
CR-SQLite adds functionality that allows us to request a set of changes from an SQLite database, and then we can insert those changes to another copy of the SQLite database, likely running on another device.
It works in two steps:
CRRs
(conflict-free replicated relations) with the following command:
SELECT crsql_as_crr('todo');
SELECT * FROM crsql_changes
The results can be merged in any order (they are commutative) and the databases will always converge on the same state given the same set of operations.
The example project is a todo list: expo/todo-sync-example. There are two parts:
expo-sqlite
and TinyBase to store the user's todo list.To run the example project, clone the example repo, check out the initial-poc
branch, and run yarn
to install its dependencies (this project is a Yarn Classic workspace).
After that, start the server:
cd apps/server
yarn start
Then, start the app:
cd apps/mobile
npx expo run:ios
We now have the app running in the iOS simulator. Let's start a second instance of the app on another simulator so that we can see the sync in action:
npx expo run:ios -d # Select another device from the prompt
Make some changes, add and delete todos, mark them as complete, or delete everything. No matter which device you use, you will see both stay in sync.
The first step is to set up the TinyBase persister.
// App.tsx
import { useCreatePersister } from 'tinybase/lib/ui-react';
import { createExpoSqlitePersister } from './app/store';
function TodoList() {
// ...
useCreatePersister(
store,
(store) =>
createExpoSqlitePersister(store, db, {
mode: 'tabular',
tables: {
load: { todo: { tableId: 'todo', rowIdColumnName: 'id' } },
save: { todo: { tableName: 'todo', rowIdColumnName: 'id' } },
},
}),
[db],
async (persister) => {
await persister.startAutoLoad();
await persister.startAutoSave();
}
);
}
The createExpoSqlitePersister()
function allows TinyBase to interact with the underlying data store, which is expo-sqlite
in this case. As we make changes to our store, changes will be persisted in the local SQLite database. This is everything we need to set up persisting our data locally.
Next, we need to notify the server of our changes. We'll use the useSync()
hook and the onDatabaseChange()
listener provided by expo-sqlite
.
First, create a socket:
// apps/mobile/app/useSync.ts
import { useEffect, useRef } from "react";
import PartySocket from "partysocket";
export function useSync() {
const socket = useRef(createPartySocket()).current;
// ...
}
Then let's connect our server:
// apps/mobile/app/useSync.ts
export function useSync() {
// ...
useEffect(() => {
const handleMessage = (e: MessageEvent<string>) => {
if (!syncEnabled) return;
handleMessageAsync(e);
};
socket.addEventListener("message", handleMessage);
if (syncEnabled) {
// Send an init message to get the latest changes
socket.send("init");
}
return () => {
socket.removeEventListener("message", handleMessage);
};
}, [socket, syncEnabled]);
}
async function handleMessageAsync(e: MessageEvent<string>) {
const data = JSON.parse(e.data);
const rows = data[0].rows;
for (const row of rows) {
const { pk, ...rest } = row;
const sql = `INSERT INTO crsql_changes ("table", 'pk', 'cid', 'val', 'col_version', 'db_version', 'site_id') VALUES (?, ${pk}, ?, ?, ?, ?, ?)`;
try {
await db.execAsync(
[
{
sql,
args: Object.values(rest),
},
],
false
);
} catch (e) {
console.log(e);
}
}
}
We register a handler for the message
event, and when we receive it, we insert the results into our database. Finally, we use another effect to set up the onDatabaseChange()
event listener so that we are notified when the database has changed. When we receive an update event, we request our changes from the crsql_changes
table and send the results. Allowing enabling and disabling of the sync is optional. Also, note that the queries used here will be improved in future versions so users won't have to know about these details.
// apps/mobile/app/useSync.ts
export function useSync() {
// ...
useEffect(() => {
const maybeSendChanges = async () => {
if (syncEnabled) {
const changes = await requestChanges();
socket.send(JSON.stringify(changes));
}
};
// Subscribe to changes
const subscription = db.onDatabaseChange(async (result) => {
if (result.tableName.includes("__crsql_")) return;
maybeSendChanges();
});
// Also maybe send them right away, in case changes happened while sync was
// disabled
maybeSendChanges();
return () => subscription.remove();
}, [syncEnabled]);
}
async function requestChanges() {
return await db.execAsync(
[
{
sql: `SELECT "table", quote(pk) as pk, cid, val, col_version, db_version, site_id FROM crsql_changes WHERE db_version > -1`,
args: [],
},
],
false
);
}
Implementation note: a real-world application would rarely want to use WHERE db_version > -1
because this will select the entire set of changes from the crsql_changes
table, rather than only the changes that have been applied since the most recent sync (for example: WHERE db_version > ?last_sent_version
). We also left out WHERE site_id IS NULL
, which is likely be used in order to ensure we only select changes that occurred on the local client, rather than re-sending changes received from a recent sync. The code is simplified here for the sake of this proof of concept, where we have not set up an state persistence on the sync server. We'll continue to iterate on the main
branch to create an example that better represents a real app.
We plan to begin investing heavily in our SQLite bindings, expo-sqlite
, working with Matt Wonlaw on integrating seamlessly with CR-SQLite, and coordinating with James Pearce on a TinyBase persister. We're big believers in local-first architecture, and you should expect to see more work from us in this space in the future.
We'll continue to evolve this example to address many of the current limitations and to push more of the generic implementation details into related libraries. In particular, we plan to update it to:
Send us your feedback on Discord, @expo, Threads, or Bluesky.
More
Proof of concept: Expo CLI Dev Tools Plugins
Aug 10, 2023 by
Kudo Chien
Expo Orbit: Download and launch builds
Aug 9, 2023 by
Gabriel Donadel
Rollouts for EAS Update
Aug 8, 2023 by
Quinlan Jung
useUpdates() API for expo-updates
Aug 8, 2023 by
Doug Lowder
Single Sign-On (SSO)
Aug 8, 2023 by
Will Schurman
New Dashboard Tables and More Timeline Activities
Aug 2, 2023 by
Tomasz Czajecki
Enforcement of EAS Free plan limits
Aug 1, 2023 by
Chris Jensen
Expo VS Code theme
Jul 24, 2023 by
Bartosz Kaszubowski
Link your GitHub repo with Expo
Jul 19, 2023 by
Juwan Wheatley
Improved Expo Docs organization
Jul 5, 2023 by
Aman Mittal
App.js 2023
May 3, 2023 by
Jon Samp
Changes summary Q4 2022
Jan 31, 2023 by
Jon Samp