Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onyx derived values #56891

Merged
merged 35 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f57151d
Bump type-fest for NonEmptyTuple
roryabraham Feb 14, 2025
0733f7d
Create simplified implementation storing derived values directly in Onyx
roryabraham Feb 14, 2025
f05f33c
fix types and remove await
roryabraham Feb 14, 2025
8a576be
Implement example
roryabraham Feb 14, 2025
502b3c6
Add type assertions
roryabraham Feb 14, 2025
4c343ef
Prevent circular dependencies
roryabraham Feb 14, 2025
fe56c68
Include key in config for straightforward inferencing
roryabraham Feb 14, 2025
f505c0b
Merge branch 'main' into Rory-OnyxDerivedV2
roryabraham Feb 17, 2025
cb92cfc
Update README with instructions for adding new derived values
roryabraham Feb 17, 2025
4817676
Bump type-fest to latest cuz why not
roryabraham Feb 17, 2025
05e17ff
Fix withToggleVisibilityView types after type-fest upgrade
roryabraham Feb 17, 2025
7de6fc5
Add initOnyxDerivedValues to ReportUtilsTest
roryabraham Feb 17, 2025
1cb4cbe
Set concierge chat report in Onyx to fix ReportUtilsTest
roryabraham Feb 17, 2025
d3e7010
Fix navigateAfterOnboardingTest
roryabraham Feb 17, 2025
1ab4d35
Added a spot of logging
roryabraham Feb 17, 2025
a922c34
Add top-level comment
roryabraham Feb 17, 2025
c045ef2
Add unit tests
roryabraham Feb 17, 2025
867f5e4
Add logs for if derived value is restored from disk
roryabraham Feb 17, 2025
b96d4a6
Cleanup in libs/actions/Report as well
roryabraham Feb 17, 2025
0d08483
Merge branch 'main' into Rory-OnyxDerivedV2
roryabraham Feb 18, 2025
27ece8d
Split up dervied value configs into their own files
roryabraham Feb 18, 2025
25bdbf4
Use OnyxValue instead of GetOnyxTypeForKey
roryabraham Feb 18, 2025
aed0d95
Use OnyxUtils.tupleGet, remove unused GetOnyxTypeForKey
roryabraham Feb 18, 2025
446fead
Move ONYX_DERIVED_VALUES to its own file
roryabraham Feb 18, 2025
2a76669
Bump Onyx for OnyxUtils.tupleGet
roryabraham Feb 18, 2025
0a91c78
Fix lint
roryabraham Feb 18, 2025
d226365
Merge branch 'main' into Rory-OnyxDerivedV2
roryabraham Feb 21, 2025
c040ec0
Include current value as second arg to compute
roryabraham Feb 21, 2025
140c793
Remove readonly
roryabraham Feb 21, 2025
b9f1d50
Merge branch 'main' into Rory-OnyxDerivedV2
roryabraham Feb 24, 2025
88ba460
Return early if conciergeChatReportID is already computed
roryabraham Feb 24, 2025
9e57693
Simplify types using satisfies
roryabraham Feb 24, 2025
5c609d7
Merge branch 'main' into Rory-OnyxDerivedV2
roryabraham Feb 26, 2025
2576aeb
Don't always rely on previously computed value
roryabraham Feb 27, 2025
bf9c5e6
Merge branch 'main' into Rory-OnyxDerivedV2
roryabraham Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -854,3 +854,14 @@ In order to compile a production iOS build, run `npm run ios-build`, this will g

#### Local production build the Android app
To build an APK to share run (e.g. via Slack), run `npm run android-build`, this will generate a new APK in the `android/app` folder.

# Onyx derived values
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind moving this to the section specifically about Onyx? https://github.com/Expensify/App?tab=readme-ov-file#onyx

Onyx derived values are special Onyx keys which contain values derived from other Onyx values. These are available as a performance optimization, so that if the result of a common computation of Onyx values is needed in many places across the app, the computation can be done only as needed in a centralized location, and then shared across the app. Once created, Onyx derived values are stored and consumed just like any other Onyx value.

## Creating new Onyx derived values
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add a little bit more to this section to explain why you should or shouldn't use derived values. Would you mind adding a bit of details like:

### When should I use derived values?

### When should I avoid using derived values?

1. Add the new Onyx key. The keys for Onyx derived values are stored in `ONYXKEYS.ts`, in the `ONYXKEYS.DERIVED` object.
2. Declare the type for the derived value in `ONYXKEYS.ts`, in the `OnyxDerivedValuesMapping` type.
3. Add the derived value config to `ONYX_DERIVED_VALUES` in `src/libs/OnyxDerived.ts`. A derived value config is defined by:
1. The Onyx key for the derived value
2. An array of dependent Onyx keys (which can be any keys, not including the one from the previous step. Including other derived values!)
3. A `compute` function, which takes an array of dependent Onyx values (in the same order as the array of keys from the previous step), and returns a value matching the type you declared in `OnyxDerivedValuesMapping`
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"type-fest": "4.20.0",
"type-fest": "4.35.0",
"typescript": "^5.4.5",
"wait-port": "^0.2.9",
"webpack": "^5.94.0",
Expand Down
51 changes: 48 additions & 3 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import type CONST from './CONST';
import type {OnboardingCompanySize} from './CONST';
Expand Down Expand Up @@ -759,6 +760,9 @@ const ONYXKEYS = {
WORKSPACE_PER_DIEM_FORM: 'workspacePerDiemForm',
WORKSPACE_PER_DIEM_FORM_DRAFT: 'workspacePerDiemFormDraft',
},
DERIVED: {
CONCIERGE_CHAT_REPORT_ID: 'conciergeChatReportID',
},
} as const;

type AllOnyxKeys = DeepValueOf<typeof ONYXKEYS>;
Expand Down Expand Up @@ -1079,20 +1083,61 @@ type OnyxValuesMapping = {
[ONYXKEYS.LAST_FULL_RECONNECT_TIME]: string;
[ONYXKEYS.TRAVEL_PROVISIONING]: OnyxTypes.TravelProvisioning;
};
type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping;

type OnyxDerivedValuesMapping = {
[ONYXKEYS.DERIVED.CONCIERGE_CHAT_REPORT_ID]: string;
};

type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping & OnyxDerivedValuesMapping;

type OnyxCollectionKey = keyof OnyxCollectionValuesMapping;
type OnyxFormKey = keyof OnyxFormValuesMapping;
type OnyxFormDraftKey = keyof OnyxFormDraftValuesMapping;
type OnyxValueKey = keyof OnyxValuesMapping;
type OnyxDerivedKey = keyof OnyxDerivedValuesMapping;

type OnyxKey = OnyxValueKey | OnyxCollectionKey | OnyxFormKey | OnyxFormDraftKey;
type OnyxKey = OnyxValueKey | OnyxCollectionKey | OnyxFormKey | OnyxFormDraftKey | OnyxDerivedKey;
type OnyxPagesKey = typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES;

type GetOnyxTypeForKey<K extends OnyxKey> =
// Forms (and draft forms) behave like value keys
K extends OnyxFormKey
? OnyxEntry<OnyxFormValuesMapping[K]>
: K extends OnyxFormDraftKey
? OnyxEntry<OnyxFormDraftValuesMapping[K]>
: // Plain non-collection values
K extends OnyxValueKey
? OnyxEntry<OnyxValuesMapping[K]>
: K extends OnyxDerivedKey
? OnyxEntry<OnyxDerivedValuesMapping[K]>
: // Exactly matching a collection key returns a collection
K extends OnyxCollectionKey
? OnyxCollection<OnyxCollectionValuesMapping[K]>
: // Otherwise, if K is a string that starts with one of the collection keys,
// return an entry for that collection’s value type.
K extends string
? {
[X in OnyxCollectionKey]: K extends `${X}${string}` ? OnyxEntry<OnyxCollectionValuesMapping[X]> : never;
}[OnyxCollectionKey]
: never;

type MissingOnyxKeysError = `Error: Types don't match, OnyxKey type is missing: ${Exclude<AllOnyxKeys, OnyxKey>}`;
/** If this type errors, it means that the `OnyxKey` type is missing some keys. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type AssertOnyxKeys = AssertTypesEqual<AllOnyxKeys, OnyxKey, MissingOnyxKeysError>;

export default ONYXKEYS;
export type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxFormDraftKey, OnyxFormKey, OnyxFormValuesMapping, OnyxKey, OnyxPagesKey, OnyxValueKey, OnyxValues};
export type {
OnyxCollectionKey,
OnyxCollectionValuesMapping,
OnyxFormDraftKey,
OnyxFormKey,
OnyxFormValuesMapping,
OnyxKey,
OnyxPagesKey,
OnyxValueKey,
OnyxValues,
OnyxDerivedKey,
GetOnyxTypeForKey,
OnyxDerivedValuesMapping,
};
9 changes: 4 additions & 5 deletions src/components/withToggleVisibilityView.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import type {ComponentType, ForwardedRef, ReactElement, RefAttributes} from 'react';
import React from 'react';
import {View} from 'react-native';
import type {SetOptional} from 'type-fest';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes were only necessary because I upgraded type-fest and a bug fix in that repo exposed existing issues with the types in this code.

import useThemeStyles from '@hooks/useThemeStyles';
import getComponentDisplayName from '@libs/getComponentDisplayName';

type WithToggleVisibilityViewProps = {
/** Whether the content is visible. */
isVisible: boolean;
isVisible?: boolean;
};

export default function withToggleVisibilityView<TProps extends WithToggleVisibilityViewProps, TRef>(
export default function withToggleVisibilityView<TProps, TRef>(
WrappedComponent: ComponentType<TProps & RefAttributes<TRef>>,
): (props: TProps & RefAttributes<TRef>) => ReactElement | null {
function WithToggleVisibilityView({isVisible = false, ...rest}: SetOptional<TProps, 'isVisible'>, ref: ForwardedRef<TRef>) {
): (props: TProps & WithToggleVisibilityViewProps & RefAttributes<TRef>) => ReactElement | null {
function WithToggleVisibilityView({isVisible = false, ...rest}: WithToggleVisibilityViewProps, ref: ForwardedRef<TRef>) {
const styles = useThemeStyles();
return (
<View
Expand Down
20 changes: 6 additions & 14 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -731,10 +731,12 @@ let isAnonymousUser = false;
// Example case: when we need to get a report name of a thread which is dependent on a report action message.
const parsedReportActionMessageCache: Record<string, string> = {};

let conciergeChatReportID: string | undefined;
let conciergeChatReportID: OnyxEntry<string>;
Onyx.connect({
key: ONYXKEYS.CONCIERGE_REPORT_ID,
callback: (value) => (conciergeChatReportID = value),
key: ONYXKEYS.DERIVED.CONCIERGE_CHAT_REPORT_ID,
callback: (value) => {
conciergeChatReportID = value;
},
});

const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';
Expand Down Expand Up @@ -1538,21 +1540,11 @@ function getReportNotificationPreference(report: OnyxEntry<Report>): ValueOf<typ
return participant?.notificationPreference ?? CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
}

const CONCIERGE_ACCOUNT_ID_STRING = CONST.ACCOUNT_ID.CONCIERGE.toString();
/**
* Only returns true if this is our main 1:1 DM report with Concierge.
*/
function isConciergeChatReport(report: OnyxInputOrEntry<Report>): boolean {
if (!report?.participants || isThread(report)) {
return false;
}

const participantAccountIDs = new Set(Object.keys(report.participants));
if (participantAccountIDs.size !== 2) {
return false;
}

return participantAccountIDs.has(CONCIERGE_ACCOUNT_ID_STRING) || report?.reportID === conciergeChatReportID;
return !!report && report?.reportID === conciergeChatReportID;
}

function findSelfDMReportID(): string | undefined {
Expand Down
183 changes: 183 additions & 0 deletions src/libs/actions/OnyxDerived.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* This file contains logic for derived Onyx keys. The idea behind derived keys is that if there is a common computation
* that we're doing in many places across the app to derive some value from multiple Onyx values, we can move that
* computation into this file, run it only once, and then share it across the app by storing the result of that computation in Onyx.
*
* The primary purpose is to optimize performance by reducing redundant computations. More info can be found in the README.
*/
import type {OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';
import type {NonEmptyTuple, ValueOf} from 'type-fest';
import Log from '@libs/Log';
import {isThread} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import type {GetOnyxTypeForKey, OnyxDerivedKey, OnyxDerivedValuesMapping, OnyxKey} from '@src/ONYXKEYS';
import ONYXKEYS from '@src/ONYXKEYS';
import type AssertTypesEqual from '@src/types/utils/AssertTypesEqual';
import ObjectUtils from '@src/types/utils/ObjectUtils';
import type SymmetricDifference from '@src/types/utils/SymmetricDifference';

/**
* A derived value configuration describes:
* - a tuple of Onyx keys to subscribe to (dependencies),
* - a compute function that derives a value from the dependent Onyx values.
* The compute function receives a single argument that's a tuple of the onyx values for the declared dependencies.
* For example, if your dependencies are `['report_', 'account'], then compute will receive a [OnyxCollection<Report>, OnyxEntry<Account>]
*/
type OnyxDerivedValueConfig<Key extends ValueOf<typeof ONYXKEYS.DERIVED>, Deps extends NonEmptyTuple<Exclude<OnyxKey, Key>>> = {
key: Key;
dependencies: Deps;
compute: (args: {
-readonly [Index in keyof Deps]: GetOnyxTypeForKey<Deps[Index]>;
}) => OnyxEntry<OnyxDerivedValuesMapping[Key]>;
};

/**
* Helper function to create a derived value config. This function is just here to help TypeScript infer Deps, so instead of writing this:
*
* const conciergeChatReportIDConfig: OnyxDerivedValueConfig<[typeof ONYXKEYS.COLLECTION.REPORT, typeof ONYXKEYS.CONCIERGE_REPORT_ID]> = {
* dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID],
* ...
* };
*
* We can just write this:
*
* const conciergeChatReportIDConfig = createOnyxDerivedValueConfig({
* dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID]
* })
*/
function createOnyxDerivedValueConfig<Key extends ValueOf<typeof ONYXKEYS.DERIVED>, Deps extends NonEmptyTuple<Exclude<OnyxKey, Key>>>(
config: OnyxDerivedValueConfig<Key, Deps>,
): OnyxDerivedValueConfig<Key, Deps> {
return config;
}

/**
* Global map of derived configs.
* This object holds our derived value configurations.
*/
const ONYX_DERIVED_VALUES = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a separate file. We will be adding more to it in future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated directory structure:

src/libs/actions/OnyxDerived
--| configs                           // you can add more dervied value configs in here, one config per file
----| conciergeChatReportID.ts
--| createOnyxDerivedValueConfig.ts   // this utility is in it's own file to prevent a require cycle. it's imported by all the configs, and those are imported by OnyxDerived/index.ts
--| index.ts                          // the main utility which handles computing all the values, setting up subscriptions, etc...
--| ONYX_DERIVED_VALUES.ts            // the global map of derived value configs. Mostly just imports configs from ./configs and puts them in a map, but also includes some advanced type assertions
--| types.ts                          // contains reusable types for the module

[ONYXKEYS.DERIVED.CONCIERGE_CHAT_REPORT_ID]: createOnyxDerivedValueConfig({
key: ONYXKEYS.DERIVED.CONCIERGE_CHAT_REPORT_ID,
dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID],
compute: ([reports, conciergeChatReportID]) => {
if (!reports) {
return undefined;
}

const conciergeReport = Object.values(reports).find((report) => {
if (!report?.participants || isThread(report)) {
return false;
}

const participantAccountIDs = new Set(Object.keys(report.participants));
if (participantAccountIDs.size !== 2) {
return false;
}

return participantAccountIDs.has(CONST.ACCOUNT_ID.CONCIERGE.toString()) || report?.reportID === conciergeChatReportID;
});

return conciergeReport?.reportID;
},
}),
} as const;

/**
* This helper exists to map an array of Onyx keys such as `['report_', 'conciergeReportID']`
* to the values for those keys (correctly typed) such as `[OnyxCollection<Report>, OnyxEntry<string>]`
*
* Note: just using .map, you'd end up with `Array<OnyxCollection<Report>|OnyxEntry<string>>`, which is not what we want. This preserves the order of the keys provided.
*/
function getOnyxValues<Keys extends readonly OnyxKey[]>(keys: Keys): Promise<{[Index in keyof Keys]: GetOnyxTypeForKey<Keys[Index]>}> {
return Promise.all(keys.map((key) => OnyxUtils.get(key))) as Promise<{[Index in keyof Keys]: GetOnyxTypeForKey<Keys[Index]>}>;
}

/**
* Initialize all Onyx derived values, store them in Onyx, and setup listeners to update them when dependencies change.
*/
function init() {
for (const [key, {compute, dependencies}] of ObjectUtils.typedEntries(ONYX_DERIVED_VALUES)) {
// Create an array to hold the current values for each dependency.
// We cast its type to match the tuple expected by config.compute.
let dependencyValues = new Array(dependencies.length) as Parameters<typeof compute>[0];

OnyxUtils.get(key).then((storedDerivedValue) => {
let derivedValue = storedDerivedValue;
if (derivedValue) {
Log.info(`Derived value ${derivedValue} for ${key} restored from disk`);
} else {
getOnyxValues(dependencies).then((values) => {
dependencyValues = values;
derivedValue = compute(values);
Onyx.set(key, derivedValue ?? null);
});
}

const setDependencyValue = <Index extends number>(i: Index, value: Parameters<typeof compute>[0][Index]) => {
dependencyValues[i] = value;
};

const recomputeDerivedValue = () => {
const newDerivedValue = compute(dependencyValues);
if (newDerivedValue !== derivedValue) {
Log.info(`[OnyxDerived] value for key ${key} changed, updating it in Onyx`, false, {old: derivedValue, new: newDerivedValue});
derivedValue = newDerivedValue;
Onyx.set(key, derivedValue ?? null);
}
};

for (let i = 0; i < dependencies.length; i++) {
const dependencyOnyxKey = dependencies[i];
if (OnyxUtils.isCollectionKey(dependencyOnyxKey)) {
Onyx.connect({
key: dependencyOnyxKey,
waitForCollectionCallback: true,
callback: (value) => {
Log.info(`[OnyxDerived] dependency ${dependencyOnyxKey} for derived key ${key} changed, recomputing`);
setDependencyValue(i, value);
recomputeDerivedValue();
},
});
} else {
Onyx.connect({
key: dependencyOnyxKey,
callback: (value) => {
Log.info(`[OnyxDerived] dependency ${dependencyOnyxKey} for derived key ${key} changed, recomputing`);
setDependencyValue(i, value);
recomputeDerivedValue();
},
});
}
}
});
}
}

export default init;

// Note: we can't use `as const satisfies...` for ONYX_DERIVED_VALUES without losing type specificity.
// So these type assertions are here to help enforce that ONYX_DERIVED_VALUES has all the keys and the correct types,
// according to the type definitions for derived keys in ONYXKEYS.ts.
type MismatchedDerivedKeysError =
`Error: ONYX_DERIVED_VALUES does not match ONYXKEYS.DERIVED or OnyxDerivedValuesMapping. The following keys are present in one or the other, but not both: ${SymmetricDifference<
keyof typeof ONYX_DERIVED_VALUES,
OnyxDerivedKey
>}`;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type KeyAssertion = AssertTypesEqual<keyof typeof ONYX_DERIVED_VALUES, OnyxDerivedKey, MismatchedDerivedKeysError>;

type ExpectedDerivedValueComputeReturnTypes = {
[Key in keyof OnyxDerivedValuesMapping]: OnyxEntry<OnyxDerivedValuesMapping[Key]>;
};
type ActualDerivedValueComputeReturnTypes = {
[Key in keyof typeof ONYX_DERIVED_VALUES]: ReturnType<(typeof ONYX_DERIVED_VALUES)[Key]['compute']>;
};
type MismatchedDerivedValues = {
[Key in keyof ExpectedDerivedValueComputeReturnTypes]: ExpectedDerivedValueComputeReturnTypes[Key] extends ActualDerivedValueComputeReturnTypes[Key] ? never : Key;
}[keyof ExpectedDerivedValueComputeReturnTypes];
type MismatchedDerivedValuesError =
`Error: ONYX_DERIVED_VALUES does not match OnyxDerivedValuesMapping. The following configs have compute functions that do not return the correct type according to OnyxDerivedValuesMapping: ${MismatchedDerivedValues}`;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type ComputeReturnTypeAssertion = AssertTypesEqual<MismatchedDerivedValues, never, MismatchedDerivedValuesError>;
3 changes: 3 additions & 0 deletions src/setup/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {I18nManager} from 'react-native';
import Onyx from 'react-native-onyx';
import initOnyxDerivedValues from '@libs/actions/OnyxDerived';
import intlPolyfill from '@libs/IntlPolyfill';
import {setDeviceID} from '@userActions/Device';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -45,6 +46,8 @@ export default function () {
skippableCollectionMemberIDs: CONST.SKIPPABLE_COLLECTION_MEMBER_IDS,
});

initOnyxDerivedValues();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't it add to the startup time of the app? Earlier these callbacks were async, now we are adding these subscriptions on startup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a trade-off we discussed, and I had an alternate solution that supported lazy-loading derived values. We opted for this one for simplicity.

However, in practice it doesn't increase startup time, because the derived values we are using are mostly all needed during startup anyways. And if the same thing is computed N times for N re-renders during startup, v.s: just once (or more accurately, K times, where K is the number of dependencies on the derived value), then the latter will be faster.

Here's a benchmark that showed that this exactly solution (applied on more derived values than the one included in this PR) improves TTI and other metrics. (ref)

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying. It is good we already discussed this.


setDeviceID();

// Force app layout to work left to right because our design does not currently support devices using this mode
Expand Down
Loading
Loading