MaxAst / expo-share-extension

Expo config plugin for creating iOS share extensions with a custom view.
MIT License
215 stars 5 forks source link

Share extension works in simulator but does not open in release builds #51

Open ravensroom opened 2 days ago

ravensroom commented 2 days ago

In simulator, everything function works beautifully.

In a release build, the share extension won't open. Like this:

https://github.com/user-attachments/assets/98a8e8e3-179d-435c-a986-93ff0ad07b8f

I learned about 100 MB memory limit for share extension in another issue thread, so, If I minimize my code to include only a react native view that has a text and text input field. It DOES work. Adding one more line to call "openHostApp" on press, will cause it to consistently not open in production build Adding other functions will too. I'm still doing more experiments.

If this is a memory usage issue, then I don't know how to keep developing because the current code is already very minimum.

app.json

[
        "expo-share-extension",
        {
          "activationRules": [
            {
              "type": "image",
              "max": 1
            },
            {
              "type": "text"
            },
            {
              "type": "url",
              "max": 1
            }
          ]
        }
      ],

package.json:

{
  "name": "x",
  "version": "1.4.4841",
  "main": "src/index.tsx",
  "scripts": {
    "postinstall": "patch-package && npm run codegen:static",
    "start": "expo start",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "clean": "rm -rf src/__generated__ schema.graphql .easignore .expo",
    "clean:generated": "rm -rf src/__generated__",
    "codegen": "npm run clean:generated && tsx ./src/scripts/codegen.ts --endpoint http://localhost:8080/graphql",
    "codegen:static": "npm run clean:generated && tsx ./src/scripts/codegen.ts",
    "codegen:api": "tsx ./src/scripts/codegenApi.ts",
    "codegen:store": "tsx ./src/scripts/codegenStore.ts",
    "typecheck": "tsc --noEmit",
    "lint": "eslint --max-warnings 0 .",
    "unit": "jest --config ./jest.config.js",
    "format-code": "prettier --write . \"**/*.svg\"",
    "format:check": "prettier --check . \"**/*.svg\"",
    "test": "npm run typecheck && npm run lint && npm run unit && npm run format:check",
    "release": "./create-release.sh --auto-submit",
    "build:dev": "./build.sh --profile development-simulator",
    "build:dev:device": "./build.sh --profile development"
  },
  "dependencies": {
    "@apollo/client": "^3.9.7",
    "@gorhom/bottom-sheet": "^5.0.2",
    "@intl/t": "^1.1.0",
    "@react-native-async-storage/async-storage": "1.23.1",
    "@react-native-community/datetimepicker": "8.0.1",
    "@react-native-cookies/cookies": "^6.2.1",
    "@react-native-google-signin/google-signin": "^12.2.1",
    "@sentry/react-native": "~5.32.0",
    "@tamagui/config": "^1.97.1",
    "@tamagui/font-inter": "^1.97.1",
    "@tamagui/lucide-icons": "^1.97.1",
    "@tanstack/react-query": "^4.36.1",
    "date-fns": "^4.1.0",
    "diff": "^5.2.0",
    "esbuild": "^0.19.12",
    "expo": "^51.0.36",
    "expo-apple-authentication": "~6.4.2",
    "expo-application": "~5.9.1",
    "expo-av": "~14.0.7",
    "expo-blur": "~13.0.2",
    "expo-build-properties": "~0.12.5",
    "expo-calendar": "~13.0.5",
    "expo-camera": "~15.0.16",
    "expo-constants": "~16.0.2",
    "expo-dev-client": "~4.0.27",
    "expo-device": "~6.0.2",
    "expo-file-system": "~17.0.1",
    "expo-font": "~12.0.4",
    "expo-haptics": "~13.0.1",
    "expo-image": "~1.13.0",
    "expo-image-picker": "~15.0.7",
    "expo-linear-gradient": "~13.0.2",
    "expo-linking": "~6.3.1",
    "expo-location": "~17.0.1",
    "expo-media-library": "~16.0.5",
    "expo-notifications": "~0.28.18",
    "expo-router": "~3.5.23",
    "expo-screen-capture": "~6.0.1",
    "expo-share-extension": "^1.10.5",
    "expo-splash-screen": "~0.27.6",
    "expo-status-bar": "~1.12.1",
    "expo-system-ui": "~3.0.7",
    "expo-task-manager": "~11.8.2",
    "expo-web-browser": "~13.0.3",
    "graphql": "^15.8.0",
    "graphql-ws": "^5.16.0",
    "htmlparser2": "^9.1.0",
    "lottie-react-native": "6.7.0",
    "react": "18.2.0",
    "react-native": "0.74.5",
    "react-native-appsflyer": "^6.14.3",
    "react-native-device-info": "^11.1.0",
    "react-native-gesture-handler": "~2.20.0",
    "react-native-mmkv": "2.12.2",
    "react-native-pager-view": "6.3.0",
    "react-native-reanimated": "~3.10.1",
    "react-native-reanimated-carousel": "^3.5.1",
    "react-native-safe-area-context": "4.10.5",
    "react-native-screens": "3.31.1",
    "react-native-svg": "15.2.0",
    "react-native-tab-view": "^3.5.2",
    "react-native-webview": "^13.10.3",
    "remark-gfm": "^4.0.0",
    "remark-parse": "^11.0.0",
    "semver-compare": "^1.0.0",
    "tamagui": "^1.97.1",
    "timezones-list": "^3.0.3",
    "unified": "^11.0.4",
    "web-streams-polyfill": "^4.0.0",
    "zustand": "^4.5.4"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
    "@babel/preset-env": "^7.24.8",
    "@babel/preset-react": "^7.24.7",
    "@ianvs/prettier-plugin-sort-imports": "^4.2.0",
    "@testing-library/react-native": "^12.5.1",
    "@total-typescript/ts-reset": "^0.5.1",
    "@types/babel__core": "^7.20.5",
    "@types/diff": "^5.2.1",
    "@types/jest": "^29.5.12",
    "@types/react": "~18.2.79",
    "@types/react-test-renderer": "^18.1.0",
    "@types/semver-compare": "^1.0.3",
    "@typescript-eslint/eslint-plugin": "^7.2.0",
    "@typescript-eslint/parser": "^7.2.0",
    "apollo": "^2.34.0",
    "eslint": "^8.57.0",
    "eslint-plugin-eslint-comments": "^3.2.0",
    "eslint-plugin-import": "^2.29.1",
    "eslint-plugin-react": "^7.34.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-t": "^1.7.1",
    "eslint-plugin-unicorn": "^55.0.0",
    "jest": "^29.7.0",
    "jest-expo": "~51.0.4",
    "jsdom": "^24.0.0",
    "patch-package": "^8.0.0",
    "prettier": "^3.0.3",
    "react-native-svg-transformer": "^1.3.0",
    "react-test-renderer": "18.2.0",
    "tsx": "^4.7.1",
    "typescript": "~5.3.3"
  },
  "overrides": {
    "graphql": "^15.8.0",
    "react-dom": "18.2.0"
  },
  "prettier": {
    "singleQuote": true,
    "overrides": [
      {
        "files": "*.svg",
        "options": {
          "parser": "html"
        }
      },
      {
        "files": [
          "app.json",
          "eas.json"
        ],
        "options": {
          "parser": "json-stringify"
        }
      }
    ],
    "plugins": [
      "@ianvs/prettier-plugin-sort-imports"
    ],
    "importOrder": [
      "^react$",
      "^react-native$",
      "<THIRD_PARTY_MODULES>",
      "",
      "^[./]"
    ]
  },
  "private": true
}

ShareExtension.tsx

export function ShareExtension(props: InitialProps) {
  const { text, images: initialUrls = [], url } = props;
  // const { images } = useUploadImages(initialUrls);
  const [showSuccess, setShowSuccess] = useState(false);

  // uncomment this will fail 
  // useEffect(() => {
  //   const { text, images, url } = props;
  //   sendLog('USER_OPEN_SHARE_TO_ARIO', {
  //     initialMessage: {
  //       text,
  //       images,
  //       url,
  //     },
  //   });
  // }, [props]);

  const onSubmit = (content: string) => {
    const message: Message = {
      id: '',
      text: content,
      // image: images[0]?.uploadedUrl ?? undefined,
      link: url,
    };
    // adding this fails
    openHostApp('/');
    // void submitMessage(message);
    // console.log('message', message);
    // const response = await submitMessage(message);
    // const { conversationId } = response;
    // if (conversationId) {
    //   message.id = conversationId;
    //   localStorage.setItem(LAST_SHARED_MESSAGE_KEY, JSON.stringify(message));
    //   sendLog('USER_QUESTION_ASKED_SUCCESS', {
    //     frontdeskMode: true,
    //     conversationId,
    //     fromShareExtension: true,
    //   });
    //   setShowSuccess(true);
    //   await clearAppGroupContainer();
    // }
  };

  return (
    <View
      style={{
        flex: 1,
        padding: 20,
        gap: 10,
      }}
    >
      {/* {images.length > 0 && <ImageView images={images} />} */}
      {url && <LinkView url={url} />}
      <ChatInput text={text ?? ''} onSubmit={onSubmit} />
      {/* {showSuccess ? <ShareSuccess /> : null} */}
    </View>
  );
}

const INPUT_HEIGHT = 180;

function ChatInput({
  text,
  onSubmit,
}: {
  text: string;
  onSubmit: (content: string) => void;
}) {
  const [content, setContent] = React.useState(text);

  return (
    <View
      style={{
        padding: 10,
      }}
    >
      <TextInput
        value={content}
        onChangeText={setContent}
        multiline={true}
        numberOfLines={4}
        placeholder={t('Add a message')}
        autoFocus
        style={{
          height: INPUT_HEIGHT,
          padding: 14,
          borderWidth: 1,
          borderRadius: 10,
          borderColor: '#ccc',
          fontSize: 16,
        }}
      />
      <Button
        title={t('Share')}
        onPress={() => {
          onSubmit(content);
          setContent('');
        }}
      />
    </View>
  );
}

function ImageView({ images }: { images: Array<ImageItem> }) {
  const renderItem = ({ item }: { item: ImageItem }) => {
    return (
      <View>
        <Image
          source={{ uri: item.initialUrl }}
          style={{
            width: 100,
            height: 100,
            borderRadius: 10,
          }}
        />
        {item.status === 'uploading' && (
          <Text style={{ color: 'blue' }}>Uploading...</Text>
        )}
        {item.status === 'success' && (
          <Text style={{ color: 'green' }}>Uploaded</Text>
        )}
        {item.status === 'error' && (
          <Text style={{ color: 'red' }}>Error: {String(item.error)}</Text>
        )}
      </View>
    );
  };
  return (
    <View
      style={{
        flexDirection: 'row',
        justifyContent: 'space-around',
        padding: 10,
      }}
    >
      <FlatList
        data={images}
        keyExtractor={(item) => item.initialUrl}
        renderItem={renderItem}
        horizontal
      />
    </View>
  );
}

function LinkView({ url }: { url: string }) {
  return (
    <View
      style={{
        padding: 10,
      }}
    >
      <Text numberOfLines={2} ellipsizeMode="tail">
        {url}
      </Text>
    </View>
  );
}
MaxAst commented 1 day ago

Hey @ravensroom, can you try removing the Image from ShareExtension? I've run into a similar issue where rendering a list of Image components caused it to crash. I think expo-image is quite memory intensive on mount. Obv not great, we should be able to render images in the share extension, but just want to make sure this is indeed the issue here

ravensroom commented 1 day ago

Hey @ravensroom, can you try removing the Image from ShareExtension? I've run into a similar issue where rendering a list of Image components caused it to crash. I think expo-image is quite memory intensive on mount. Obv not great, we should be able to render images in the share extension, but just want to make sure this is indeed the issue here

hi @MaxAst Thanks for replying. Unfortunately it's not the images. The app would not open with a simple call to openHostApp or send a network request. Do you think I should try excluding all expo modules that I don't need?