diff --git a/native/account/register-panel.react.js b/native/account/register-panel.react.js
--- a/native/account/register-panel.react.js
+++ b/native/account/register-panel.react.js
@@ -52,11 +52,12 @@
 import { nativeNotificationsSessionCreator } from '../utils/crypto-utils.js';
 import { type StateContainer } from '../utils/state-container.js';
 
-export type RegisterState = {
-  +usernameInputText: string,
-  +passwordInputText: string,
-  +confirmPasswordInputText: string,
+type WritableRegisterState = {
+  usernameInputText: string,
+  passwordInputText: string,
+  confirmPasswordInputText: string,
 };
+export type RegisterState = $ReadOnly<WritableRegisterState>;
 type BaseProps = {
   +setActiveAlert: (activeAlert: boolean) => void,
   +opacityValue: Animated.Node,
@@ -240,7 +241,7 @@
   };
 
   onChangePasswordInputText = (text: string) => {
-    const stateUpdate = {};
+    const stateUpdate: Partial<WritableRegisterState> = {};
     stateUpdate.passwordInputText = text;
     if (this.passwordBeingAutoFilled) {
       this.passwordBeingAutoFilled = false;
diff --git a/native/avatars/avatar.react.js b/native/avatars/avatar.react.js
--- a/native/avatars/avatar.react.js
+++ b/native/avatars/avatar.react.js
@@ -17,6 +17,7 @@
   xxLargeAvatarSize,
 } from './avatar-constants.js';
 import Multimedia from '../media/multimedia.react.js';
+import type { ViewStyle } from '../types/styles.js';
 
 type Props = {
   +avatarInfo: ResolvedClientAvatar,
@@ -42,7 +43,10 @@
   }, [size]);
 
   const emojiContainerStyle = React.useMemo(() => {
-    const containerStyles = [styles.emojiContainer, containerSizeStyle];
+    const containerStyles: Array<ViewStyle> = [
+      styles.emojiContainer,
+      containerSizeStyle,
+    ];
     if (avatarInfo.type === 'emoji') {
       const backgroundColor = { backgroundColor: `#${avatarInfo.color}` };
       containerStyles.push(backgroundColor);
diff --git a/native/calendar/calendar.react.js b/native/calendar/calendar.react.js
--- a/native/calendar/calendar.react.js
+++ b/native/calendar/calendar.react.js
@@ -902,7 +902,7 @@
       return;
     }
 
-    const visibleEntries = {};
+    const visibleEntries: { [string]: boolean } = {};
     for (const token of info.viewableItems) {
       if (token.item.itemType === 'entryInfo') {
         visibleEntries[entryKey(token.item.entryInfo)] = true;
diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js
--- a/native/chat/chat-input-bar.react.js
+++ b/native/chat/chat-input-bar.react.js
@@ -134,7 +134,11 @@
 import { useSelector } from '../redux/redux-utils.js';
 import { type Colors, useStyles, useColors } from '../themes/colors.js';
 import type { LayoutEvent, ImagePasteEvent } from '../types/react-native.js';
-import { type AnimatedViewStyle, AnimatedView } from '../types/styles.js';
+import {
+  type AnimatedViewStyle,
+  AnimatedView,
+  type ViewStyle,
+} from '../types/styles.js';
 import Alert from '../utils/alert.js';
 import { runTiming } from '../utils/animation-utils.js';
 import { exitEditAlert } from '../utils/edit-messages-utils.js';
@@ -818,7 +822,9 @@
       </TouchableOpacity>
     );
     const threadColor = `#${this.props.threadInfo.color}`;
-    const expandoButtonsViewStyle = [this.props.styles.innerExpandoButtons];
+    const expandoButtonsViewStyle: Array<ViewStyle> = [
+      this.props.styles.innerExpandoButtons,
+    ];
     if (this.isEditMode()) {
       expandoButtonsViewStyle.push({ display: 'none' });
     }
diff --git a/native/chat/compose-subchannel.react.js b/native/chat/compose-subchannel.react.js
--- a/native/chat/compose-subchannel.react.js
+++ b/native/chat/compose-subchannel.react.js
@@ -220,7 +220,7 @@
     ],
   );
 
-  const existingThreads = React.useMemo(() => {
+  const existingThreads: $ReadOnlyArray<ThreadInfo> = React.useMemo(() => {
     if (userInfoInputIDs.length === 0) {
       return [];
     }
diff --git a/native/chat/composed-message.react.js b/native/chat/composed-message.react.js
--- a/native/chat/composed-message.react.js
+++ b/native/chat/composed-message.react.js
@@ -33,7 +33,11 @@
 import { InputStateContext } from '../input/input-state.js';
 import { useColors } from '../themes/colors.js';
 import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
-import { type AnimatedStyleObj, AnimatedView } from '../types/styles.js';
+import {
+  type AnimatedStyleObj,
+  type ViewStyle,
+  AnimatedView,
+} from '../types/styles.js';
 import { useNavigateToUserProfileBottomSheet } from '../user-profile/user-profile-utils.js';
 
 type SwipeOptions = 'reply' | 'sidebar' | 'both' | 'none';
@@ -294,7 +298,7 @@
     ]);
 
     const viewStyle = React.useMemo(() => {
-      const baseStyle = [styles.alignment];
+      const baseStyle: Array<ViewStyle> = [styles.alignment];
       if (__DEV__) {
         return baseStyle;
       }
diff --git a/native/chat/message-results-screen.react.js b/native/chat/message-results-screen.react.js
--- a/native/chat/message-results-screen.react.js
+++ b/native/chat/message-results-screen.react.js
@@ -16,6 +16,7 @@
 
 import { useHeightMeasurer } from './chat-context.js';
 import type { ChatNavigationProp } from './chat.react';
+import type { NativeChatMessageItem } from './message-data.react.js';
 import MessageResult from './message-result.react.js';
 import type { NavigationRoute } from '../navigation/route-names';
 import { useSelector } from '../redux/redux-utils.js';
@@ -68,41 +69,42 @@
     messageListData(threadInfo.id, translatedMessageResults),
   );
 
-  const sortedUniqueChatMessageInfoItems = React.useMemo(() => {
-    if (!chatMessageInfos) {
-      return [];
-    }
-
-    const chatMessageInfoItems = chatMessageInfos.filter(
-      item =>
-        item.itemType === 'message' &&
-        item.isPinned &&
-        !isInvalidPinSourceForThread(item.messageInfo, threadInfo),
-    );
-
-    // By the nature of using messageListData and passing in
-    // the desired translatedMessageResults as additional
-    // messages, we will have duplicate ChatMessageInfoItems.
-    const uniqueChatMessageInfoItemsMap = new Map();
-    chatMessageInfoItems.forEach(
-      item =>
-        item.messageInfo &&
-        item.messageInfo.id &&
-        uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item),
-    );
+  const sortedUniqueChatMessageInfoItems: $ReadOnlyArray<NativeChatMessageItem> =
+    React.useMemo(() => {
+      if (!chatMessageInfos) {
+        return [];
+      }
+
+      const chatMessageInfoItems = chatMessageInfos.filter(
+        item =>
+          item.itemType === 'message' &&
+          item.isPinned &&
+          !isInvalidPinSourceForThread(item.messageInfo, threadInfo),
+      );
 
-    // Push the items in the order they appear in the rawMessageResults
-    // since the messages fetched from the server are already sorted
-    // in the order of pin_time (newest first).
-    const sortedChatMessageInfoItems = [];
-    for (let i = 0; i < rawMessageResults.length; i++) {
-      sortedChatMessageInfoItems.push(
-        uniqueChatMessageInfoItemsMap.get(rawMessageResults[i].id),
+      // By the nature of using messageListData and passing in
+      // the desired translatedMessageResults as additional
+      // messages, we will have duplicate ChatMessageInfoItems.
+      const uniqueChatMessageInfoItemsMap = new Map();
+      chatMessageInfoItems.forEach(
+        item =>
+          item.messageInfo &&
+          item.messageInfo.id &&
+          uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item),
       );
-    }
 
-    return sortedChatMessageInfoItems.filter(Boolean);
-  }, [chatMessageInfos, rawMessageResults, threadInfo]);
+      // Push the items in the order they appear in the rawMessageResults
+      // since the messages fetched from the server are already sorted
+      // in the order of pin_time (newest first).
+      const sortedChatMessageInfoItems = [];
+      for (let i = 0; i < rawMessageResults.length; i++) {
+        sortedChatMessageInfoItems.push(
+          uniqueChatMessageInfoItemsMap.get(rawMessageResults[i].id),
+        );
+      }
+
+      return sortedChatMessageInfoItems.filter(Boolean);
+    }, [chatMessageInfos, rawMessageResults, threadInfo]);
 
   const measureCallback = React.useCallback(
     (listDataWithHeights: $ReadOnlyArray<ChatMessageItemWithHeight>) => {
diff --git a/native/chat/reaction-message-utils.js b/native/chat/reaction-message-utils.js
--- a/native/chat/reaction-message-utils.js
+++ b/native/chat/reaction-message-utils.js
@@ -115,15 +115,18 @@
   +margin: ?number,
 };
 
+type WritableContainerStyle = {
+  position: 'absolute',
+  left?: number,
+  right?: number,
+  bottom?: number,
+  top?: number,
+  ...
+};
+type ContainerStyle = $ReadOnly<WritableContainerStyle>;
+
 type ReactionSelectionPopoverPosition = {
-  +containerStyle: {
-    +position: 'absolute',
-    +left?: number,
-    +right?: number,
-    +bottom?: number,
-    +top?: number,
-    ...
-  },
+  +containerStyle: ContainerStyle,
   +popoverLocation: 'above' | 'below',
 };
 function useReactionSelectionPopoverPosition({
@@ -158,9 +161,9 @@
   const containerStyle = React.useMemo(() => {
     const { x, width, height } = initialCoordinates;
 
-    const style = {};
-
-    style.position = 'absolute';
+    const style: WritableContainerStyle = {
+      position: 'absolute',
+    };
 
     const extraLeftSpace = x;
     const extraRightSpace = windowWidth - width - x;
diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js
--- a/native/chat/robotext-message.react.js
+++ b/native/chat/robotext-message.react.js
@@ -196,7 +196,7 @@
 
   const contentAndHeaderOpacity = useContentAndHeaderOpacity(item);
 
-  const viewStyle = {};
+  const viewStyle: { height?: number } = {};
   if (!__DEV__) {
     // We don't force view height in dev mode because we
     // want to measure it in Message to see if it's correct
diff --git a/native/chat/settings/thread-settings-description.react.js b/native/chat/settings/thread-settings-description.react.js
--- a/native/chat/settings/thread-settings-description.react.js
+++ b/native/chat/settings/thread-settings-description.react.js
@@ -116,7 +116,7 @@
       this.props.descriptionEditValue !== null &&
       this.props.descriptionEditValue !== undefined
     ) {
-      const textInputStyle = {};
+      const textInputStyle: { height?: number } = {};
       if (
         this.props.descriptionTextHeight !== undefined &&
         this.props.descriptionTextHeight !== null
diff --git a/native/chat/settings/thread-settings-push-notifs.react.js b/native/chat/settings/thread-settings-push-notifs.react.js
--- a/native/chat/settings/thread-settings-push-notifs.react.js
+++ b/native/chat/settings/thread-settings-push-notifs.react.js
@@ -81,7 +81,7 @@
 
   render() {
     const componentLabel = 'Push notifs';
-    let notificationsSettingsLinkingIcon = undefined;
+    let notificationsSettingsLinkingIcon: React.Node = undefined;
     if (!this.props.hasPushPermissions) {
       notificationsSettingsLinkingIcon = (
         <TouchableOpacity
diff --git a/native/chat/swipeable-message.react.js b/native/chat/swipeable-message.react.js
--- a/native/chat/swipeable-message.react.js
+++ b/native/chat/swipeable-message.react.js
@@ -366,7 +366,7 @@
         </PanGestureHandler>
       );
     }
-    const snakes = [];
+    const snakes: Array<React.Node> = [];
     if (triggerReply) {
       snakes.push(replySwipeSnake);
     }
diff --git a/native/components/feature-flags-provider.react.js b/native/components/feature-flags-provider.react.js
--- a/native/components/feature-flags-provider.react.js
+++ b/native/components/feature-flags-provider.react.js
@@ -71,7 +71,7 @@
           5000,
         );
 
-        const configuration = {};
+        const configuration: { [string]: true } = {};
         for (const feature of config.enabledFeatures) {
           configuration[feature] = true;
         }
diff --git a/native/components/node-height-measurer.react.js b/native/components/node-height-measurer.react.js
--- a/native/components/node-height-measurer.react.js
+++ b/native/components/node-height-measurer.react.js
@@ -45,9 +45,9 @@
   +initialMeasuredHeights?: ?$ReadOnlyMap<string, number>,
   ...
 };
-type State<Item, MergedItem> = {
+type WritableState<Item, MergedItem> = {
   // These are the dummies currently being rendered
-  +currentlyMeasuring: $ReadOnlyArray<{
+  currentlyMeasuring: $ReadOnlyArray<{
     +measureKey: string,
     +dummy: React.Element<any>,
   }>,
@@ -55,16 +55,17 @@
   // avoid considering any onLayouts that got queued before we issued the
   // remeasure, we increment the "iteration" and only count onLayouts with the
   // right value
-  +iteration: number,
+  iteration: number,
   // We cache the measured heights here, keyed by measure key
-  +measuredHeights: Map<string, number>,
+  measuredHeights: Map<string, number>,
   // We cache the results of calling mergeItemWithHeight on measured items after
   // measuring their height, keyed by ID
-  +measurableItems: Map<string, MergedItemPair<Item, MergedItem>>,
+  measurableItems: Map<string, MergedItemPair<Item, MergedItem>>,
   // We cache the results of calling mergeItemWithHeight on items that aren't
   // measurable (eg. itemToKey reurns falsey), keyed by ID
-  +unmeasurableItems: Map<string, MergedItemPair<Item, MergedItem>>,
+  unmeasurableItems: Map<string, MergedItemPair<Item, MergedItem>>,
 };
+type State<Item, MergedItem> = $ReadOnly<WritableState<Item, MergedItem>>;
 class NodeHeightMeasurer<Item, MergedItem> extends React.PureComponent<
   Props<Item, MergedItem>,
   State<Item, MergedItem>,
@@ -434,7 +435,7 @@
       }
     }
 
-    const stateUpdate = {};
+    const stateUpdate: Partial<WritableState<Item, MergedItem>> = {};
     if (incrementIteration) {
       stateUpdate.iteration = this.state.iteration + 1;
     }
diff --git a/native/components/thread-ancestors-label.react.js b/native/components/thread-ancestors-label.react.js
--- a/native/components/thread-ancestors-label.react.js
+++ b/native/components/thread-ancestors-label.react.js
@@ -34,7 +34,7 @@
   );
 
   const ancestorPath = React.useMemo(() => {
-    const path = [];
+    const path: Array<React.Node> = [];
     for (const thread of resolvedAncestorThreads) {
       path.push(<Text key={thread.id}>{thread.uiName}</Text>);
       path.push(
diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js
--- a/native/input/input-state-container.react.js
+++ b/native/input/input-state-container.react.js
@@ -106,6 +106,7 @@
   InputStateContext,
   type PendingMultimediaUploads,
   type MultimediaProcessingStep,
+  type MessagePendingUploads,
 } from './input-state.js';
 import { encryptMedia } from '../media/encryption-utils.js';
 import { disposeTempFile } from '../media/file-utils.js';
@@ -123,7 +124,10 @@
   +selection: NativeMediaSelection,
   +ids: MediaIDs,
 };
-type CompletedUploads = { +[localMessageID: string]: ?Set<string> };
+type WritableCompletedUploads = {
+  [localMessageID: string]: ?$ReadOnlySet<string>,
+};
+type CompletedUploads = $ReadOnly<WritableCompletedUploads>;
 
 type BaseProps = {
   +children: React.Node,
@@ -182,7 +186,7 @@
   pendingSidebarCreationMessageLocalIDs = new Set<string>();
 
   static getCompletedUploads(props: Props, state: State): CompletedUploads {
-    const completedUploads = {};
+    const completedUploads: WritableCompletedUploads = {};
     for (const localMessageID in state.pendingUploads) {
       const messagePendingUploads = state.pendingUploads[localMessageID];
       const rawMessageInfo = props.messageStoreMessages[localMessageID];
@@ -228,7 +232,7 @@
       prevState,
     );
 
-    const newPendingUploads = {};
+    const newPendingUploads: PendingMultimediaUploads = {};
     let pendingUploadsChanged = false;
     const readyMessageIDs = [];
     for (const localMessageID in this.state.pendingUploads) {
@@ -255,7 +259,7 @@
         continue;
       }
 
-      const newUploads = {};
+      const newUploads: MessagePendingUploads = {};
       let uploadsChanged = false;
       for (const localUploadID in messagePendingUploads) {
         if (!completedUploadIDs.has(localUploadID)) {
@@ -618,7 +622,7 @@
     }
 
     const uploadFileInputs = [],
-      media = [];
+      media: Array<Media> = [];
     for (const selection of selections) {
       const localMediaID = getNextLocalUploadID();
       let ids;
@@ -656,7 +660,7 @@
       uploadFileInputs.push({ selection, ids });
     }
 
-    const pendingUploads = {};
+    const pendingUploads: MessagePendingUploads = {};
     for (const uploadFileInput of uploadFileInputs) {
       const { localMediaID } = uploadFileInput.ids;
       pendingUploads[localMediaID] = {
@@ -730,11 +734,11 @@
     const { localMediaID } = ids;
     const start = selection.sendTime;
     const steps: Array<MediaMissionStep> = [selection];
-    let encryptionSteps = [];
+    let encryptionSteps: $ReadOnlyArray<MediaMissionStep> = [];
     let serverID;
     let userTime;
     let errorMessage;
-    let reportPromise;
+    let reportPromise: ?Promise<$ReadOnlyArray<MediaMissionStep>>;
     const filesToDispose = [];
 
     const onUploadFinished = async (result: MediaMissionResult) => {
@@ -1194,7 +1198,7 @@
       'InputStateContainer.uploadBlob sent incorrect input',
     );
 
-    const parameters = {};
+    const parameters: { [key: string]: mixed } = {};
     parameters.cookie = cookie;
     parameters.filename = name;
 
diff --git a/native/media/blob-utils.js b/native/media/blob-utils.js
--- a/native/media/blob-utils.js
+++ b/native/media/blob-utils.js
@@ -118,7 +118,7 @@
   invariant(firstComma > 4, 'malformed data-URI');
   const base64String = dataURI.substring(firstComma + 1);
 
-  let mime = blob.type;
+  let mime: ?string = blob.type;
   if (!mime) {
     let mimeCheckExceptionMessage;
     const mimeCheckStart = Date.now();
diff --git a/native/media/ffmpeg.js b/native/media/ffmpeg.js
--- a/native/media/ffmpeg.js
+++ b/native/media/ffmpeg.js
@@ -46,7 +46,7 @@
   }
 
   possiblyRunCommands() {
-    let openSlots = {};
+    let openSlots: { [string]: number } = {};
     for (const type in this.currentCalls) {
       const currentCalls = this.currentCalls[type];
       const maxCalls = maxSimultaneousCalls[type];
diff --git a/native/navigation/navigation-utils.js b/native/navigation/navigation-utils.js
--- a/native/navigation/navigation-utils.js
+++ b/native/navigation/navigation-utils.js
@@ -101,7 +101,7 @@
   state: State,
   filterFunc: (route: Route) => 'keep' | 'remove' | 'break',
 ): State {
-  const newRoutes = [];
+  const newRoutes: Array<Route> = [];
   let newIndex = state.index;
   let screenRemoved = false;
   let breakActivated = false;
diff --git a/native/profile/appearance-preferences.react.js b/native/profile/appearance-preferences.react.js
--- a/native/profile/appearance-preferences.react.js
+++ b/native/profile/appearance-preferences.react.js
@@ -96,7 +96,7 @@
   render() {
     const { panelIosHighlightUnderlay: underlay } = this.props.colors;
 
-    const options = [];
+    const options: Array<React.Node> = [];
     for (let i = 0; i < optionTexts.length; i++) {
       const { themePreference, text } = optionTexts[i];
       const icon =
diff --git a/native/profile/dev-tools.react.js b/native/profile/dev-tools.react.js
--- a/native/profile/dev-tools.react.js
+++ b/native/profile/dev-tools.react.js
@@ -105,7 +105,7 @@
   render() {
     const { panelIosHighlightUnderlay: underlay } = this.props.colors;
 
-    const serverButtons = [];
+    const serverButtons: Array<React.Node> = [];
     for (const server of nodeServerOptions) {
       const icon = server === this.props.urlPrefix ? <ServerIcon /> : null;
       serverButtons.push(
diff --git a/native/push/push-handler.react.js b/native/push/push-handler.react.js
--- a/native/push/push-handler.react.js
+++ b/native/push/push-handler.react.js
@@ -249,7 +249,7 @@
     } else {
       // We do this in case there was a crash, so we can clear deviceToken from
       // any other cookies it might be set for
-      const deviceTokensMap = {};
+      const deviceTokensMap: { [string]: string } = {};
       for (const keyserverID in this.props.deviceTokens) {
         const deviceToken = this.props.deviceTokens[keyserverID];
         if (deviceToken) {
@@ -456,7 +456,7 @@
     if (deviceType === 'ios') {
       iosPushPermissionResponseReceived();
     }
-    const deviceTokensMap = {};
+    const deviceTokensMap: { [string]: ?string } = {};
     for (const keyserverID in this.props.deviceTokens) {
       const keyserverDeviceToken = this.props.deviceTokens[keyserverID];
       if (deviceToken !== keyserverDeviceToken) {
diff --git a/native/redux/dimensions-updater.react.js b/native/redux/dimensions-updater.react.js
--- a/native/redux/dimensions-updater.react.js
+++ b/native/redux/dimensions-updater.react.js
@@ -86,7 +86,8 @@
     if (keyboardShowing) {
       return;
     }
-    let updates = dimensionsUpdateFromMetrics({ frame, insets });
+    let updates: Partial<$ReadOnly<{ ...DimensionsInfo }>> =
+      dimensionsUpdateFromMetrics({ frame, insets });
     if (updates.height && updates.width && updates.height !== updates.width) {
       updates = {
         ...updates,
diff --git a/native/redux/edit-thread-permission-migration.js b/native/redux/edit-thread-permission-migration.js
--- a/native/redux/edit-thread-permission-migration.js
+++ b/native/redux/edit-thread-permission-migration.js
@@ -61,7 +61,7 @@
 function migrateThreadStoreForEditThreadPermissions(threadInfos: {
   +[id: string]: RawThreadInfo,
 }): RawThreadInfos {
-  const newThreadInfos = {};
+  const newThreadInfos: { [string]: RawThreadInfo } = {};
   for (const threadID in threadInfos) {
     const threadInfo: RawThreadInfo = threadInfos[threadID];
     const updatedMembers = threadInfo.members.map(member =>
@@ -74,7 +74,7 @@
       threadID,
     );
 
-    const updatedRoles = {};
+    const updatedRoles: { [string]: RoleInfo } = {};
     for (const roleID in threadInfo.roles) {
       updatedRoles[roleID] = addDetailedThreadEditPermissionsToRole(
         threadInfo.roles[roleID],
diff --git a/native/redux/manage-pins-permission-migration.js b/native/redux/manage-pins-permission-migration.js
--- a/native/redux/manage-pins-permission-migration.js
+++ b/native/redux/manage-pins-permission-migration.js
@@ -56,7 +56,7 @@
 function persistMigrationForManagePinsThreadPermission(
   threadInfos: ThreadStoreThreadInfos,
 ): ThreadStoreThreadInfos {
-  const newThreadInfos = {};
+  const newThreadInfos: { [string]: RawThreadInfo } = {};
   for (const threadID in threadInfos) {
     const threadInfo: RawThreadInfo = threadInfos[threadID];
     const updatedMembers = threadInfo.members.map(member =>
@@ -69,7 +69,7 @@
       threadID,
     );
 
-    const updatedRoles = {};
+    const updatedRoles: { [string]: RoleInfo } = {};
     for (const roleID in threadInfo.roles) {
       updatedRoles[roleID] = addManagePinsThreadPermissionToRole(
         threadInfo.roles[roleID],
diff --git a/native/redux/persist.js b/native/redux/persist.js
--- a/native/redux/persist.js
+++ b/native/redux/persist.js
@@ -66,7 +66,10 @@
   type ConnectionInfo,
 } from 'lib/types/socket-types.js';
 import { defaultGlobalThemeInfo } from 'lib/types/theme-types.js';
-import type { ClientDBThreadInfo } from 'lib/types/thread-types.js';
+import type {
+  ClientDBThreadInfo,
+  RawThreadInfo,
+} from 'lib/types/thread-types.js';
 import {
   translateClientDBMessageInfoToRawMessageInfo,
   translateRawMessageInfoToClientDBMessageInfo,
@@ -252,7 +255,7 @@
     },
   }),
   [19]: state => {
-    const threadInfos = {};
+    const threadInfos: { [string]: RawThreadInfo } = {};
     for (const threadID in state.threadStore.threadInfos) {
       const threadInfo = state.threadStore.threadInfos[threadID];
       const { visibilityRules, ...rest } = threadInfo;
@@ -352,7 +355,7 @@
     },
   }),
   [28]: state => {
-    const threadParentToChildren = {};
+    const threadParentToChildren: { [string]: string[] } = {};
     for (const threadID in state.threadStore.threadInfos) {
       const threadInfo = state.threadStore.threadInfos[threadID];
       const parentThreadInfo = threadInfo.parentThreadID
@@ -372,7 +375,7 @@
       return state;
     }
 
-    const threadInfos = {};
+    const threadInfos: { [string]: RawThreadInfo } = {};
     const stack = [...rootIDs];
     while (stack.length > 0) {
       const threadID = stack.shift();
@@ -884,7 +887,7 @@
       keyserverStore: { keyserverInfos },
       ...rest
     } = state;
-    const newKeyserverInfos = {};
+    const newKeyserverInfos: { [string]: KeyserverInfo } = {};
     for (const key in keyserverInfos) {
       newKeyserverInfos[key] = {
         ...keyserverInfos[key],
@@ -1029,7 +1032,7 @@
 };
 const keyserverStoreTransform: Transform = createTransform(
   (state: KeyserverStore): PersistedKeyserverStore => {
-    const keyserverInfos = {};
+    const keyserverInfos: { [string]: PersistedKeyserverInfo } = {};
     for (const key in state.keyserverInfos) {
       const { connection, ...rest } = state.keyserverInfos[key];
       keyserverInfos[key] = rest;
@@ -1040,7 +1043,7 @@
     };
   },
   (state: PersistedKeyserverStore): KeyserverStore => {
-    const keyserverInfos = {};
+    const keyserverInfos: { [string]: KeyserverInfo } = {};
     for (const key in state.keyserverInfos) {
       keyserverInfos[key] = {
         ...state.keyserverInfos[key],
diff --git a/native/redux/redux-setup.js b/native/redux/redux-setup.js
--- a/native/redux/redux-setup.js
+++ b/native/redux/redux-setup.js
@@ -67,14 +67,16 @@
         isStaff(state.currentUserInfo.id)))
   ) {
     // 1. Construct set of keys expected to be REHYDRATED
-    const defaultKeys = Object.keys(defaultState);
+    const defaultKeys: $ReadOnlyArray<string> = Object.keys(defaultState);
     const expectedKeys = defaultKeys.filter(
       each => !persistConfig.blacklist.includes(each),
     );
     const expectedKeysSet = new Set(expectedKeys);
 
     // 2. Construct set of keys actually REHYDRATED
-    const rehydratedKeys = Object.keys(action.payload ?? {});
+    const rehydratedKeys: $ReadOnlyArray<string> = Object.keys(
+      action.payload ?? {},
+    );
     const rehydratedKeysSet = new Set(rehydratedKeys);
 
     // 3. Determine the difference between the two sets
diff --git a/native/redux/remove-select-role-permissions.js b/native/redux/remove-select-role-permissions.js
--- a/native/redux/remove-select-role-permissions.js
+++ b/native/redux/remove-select-role-permissions.js
@@ -1,6 +1,10 @@
 // @flow
 
-import type { RawThreadInfos } from 'lib/types/thread-types.js';
+import type {
+  RawThreadInfos,
+  RawThreadInfo,
+  RoleInfo,
+} from 'lib/types/thread-types.js';
 import { permissionsToRemoveInMigration } from 'lib/utils/migration-utils.js';
 
 function persistMigrationToRemoveSelectRolePermissions(
@@ -13,16 +17,16 @@
     return {};
   }
 
-  const updatedThreadInfos = {};
+  const updatedThreadInfos: { [string]: RawThreadInfo } = {};
   for (const threadID in rawThreadInfos) {
     const threadInfo = rawThreadInfos[threadID];
     const { roles } = threadInfo;
 
-    const updatedRoles = {};
+    const updatedRoles: { [string]: RoleInfo } = {};
     for (const roleID in roles) {
       const role = roles[roleID];
       const { permissions: rolePermissions } = role;
-      const updatedPermissions = {};
+      const updatedPermissions: { [string]: boolean } = {};
       for (const permission in rolePermissions) {
         if (!permissionsToRemoveInMigration.includes(permission)) {
           updatedPermissions[permission] = rolePermissions[permission];
diff --git a/native/redux/update-roles-and-permissions.js b/native/redux/update-roles-and-permissions.js
--- a/native/redux/update-roles-and-permissions.js
+++ b/native/redux/update-roles-and-permissions.js
@@ -22,7 +22,7 @@
 function constructThreadTraversalNodes(
   threadStoreInfos: ThreadStoreThreadInfos,
 ): $ReadOnlyArray<$ReadOnly<ThreadTraversalNode>> {
-  const parentThreadMap = {};
+  const parentThreadMap: { [string]: Array<string> } = {};
 
   for (const threadInfo of values(threadStoreInfos)) {
     const parentThreadID = threadInfo.parentThreadID ?? 'root';
@@ -79,7 +79,9 @@
     const threadInfo: RawThreadInfo = updatedThreadStoreInfos[node.threadID];
 
     const updatedMembers = [];
-    const memberToThreadPermissionsForChildren = {};
+    const memberToThreadPermissionsForChildren: {
+      [string]: ?ThreadPermissionsBlob,
+    } = {};
     for (const member: MemberInfo of threadInfo.members) {
       const { id, role } = member;
 
diff --git a/native/themes/colors.js b/native/themes/colors.js
--- a/native/themes/colors.js
+++ b/native/themes/colors.js
@@ -283,7 +283,7 @@
   obj: IS,
   themeColors: Colors,
 ): StyleSheetOf<IS> {
-  const result = {};
+  const result: Styles = {};
   for (const key in obj) {
     const style = obj[key];
     const filledInStyle = { ...style };
diff --git a/native/tooltip/tooltip.react.js b/native/tooltip/tooltip.react.js
--- a/native/tooltip/tooltip.react.js
+++ b/native/tooltip/tooltip.react.js
@@ -368,13 +368,13 @@
         ...navAndRouteForFlow
       } = this.props;
 
-      const tooltipContainerStyle = [styles.itemContainer];
+      const tooltipContainerStyle: Array<ViewStyle> = [styles.itemContainer];
 
       if (this.tooltipLocation === 'fixed') {
         tooltipContainerStyle.push(styles.itemContainerFixed);
       }
 
-      const items = [
+      const items: Array<React.Node> = [
         <MenuComponent
           {...navAndRouteForFlow}
           tooltipItem={this.getTooltipItem()}
diff --git a/native/utils/drawer-utils.react.js b/native/utils/drawer-utils.react.js
--- a/native/utils/drawer-utils.react.js
+++ b/native/utils/drawer-utils.react.js
@@ -26,7 +26,7 @@
   expanded: $ReadOnlyArray<string>,
   prevIndentation: ?number,
 ): $ReadOnlyArray<CommunityDrawerItemDataFlattened> {
-  let results = [];
+  let results: Array<CommunityDrawerItemDataFlattened> = [];
 
   for (const item of data) {
     const isOpen = expanded.includes(item.threadInfo.id);