achorein/expo-share-intent

ShareProvider and shareIntent hook disabled prop not behaving as expected :/

Closed this issue · 6 comments

Describe the bug
Now trying to use expo go, and for some reason setting the disabled flag to true in both the hook and provider give me this error when I am importing into the _layout.tsx file.

 ERROR  Error: Cannot find native module 'ExpoShareIntentModule', js engine: hermes 
    at ContextNavigator (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:276602:24)
    at ExpoRoot (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:276558:28)
    at App
    at ErrorToastContainer (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:378383:24)
    at ErrorOverlay
    at withDevTools(ErrorOverlay) (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:377845:27)
    at RCTView
    at View (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:40974:43)
    at RCTView
    at View (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:40974:43)
    at AppContainer (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:40785:36)
    at main(RootComponent) (http://10.1.10.101:8081/node_modules/expo-router/entry.bundle//&platform=ios&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=true&transform.routerRoot=app:124770:28)

The only way I am able to have the ShareIntent module resolve correctly in my project for expo-go is calling the hook like so...

import "../utils/firebase.utils";
import { useFonts } from "expo-font";
import { SplashScreen, Stack } from "expo-router";
import { useEffect } from "react";
import { Platform } from "react-native";
import AppContainer from "../components/AppContainer/AppContainer";
import React from "react";
import { RegisterContextProviders } from "../components/RegisterContextProviders/RegisterContextProviders";
import { useTrackDeviceLocation } from "../hooks/useTrackDeviceLocation";
import { FontFamily } from "styles";
import { useAuthentication } from "@hooks/useAuthentication";
import { HomeLoadingScreen } from "@components/Loading/HomeLoadingScreen";
import Constants from "expo-constants";

export { ErrorBoundary } from "expo-router";

export const unstable_settings = {
  initialRouteName: "(tabs)",
};

if (Platform.OS !== "web") SplashScreen.preventAutoHideAsync();

// Separate component to handle share intent
const ShareIntentComponent = () => {
  const { useShareIntent } = require("expo-share-intent");
  const { hasShareIntent, shareIntent, resetShareIntent } = useShareIntent();

  useEffect(() => {
    if (hasShareIntent) {
      console.log("Received share intent:", shareIntent);
      resetShareIntent();
    }
  }, [hasShareIntent, shareIntent, resetShareIntent]);

  return null; // This component does not render anything
};

const RootLayout = () => {
  const [loaded, error] = useFonts({
    [FontFamily["DM Sans-Light"]]: require("../assets/fonts/DMSans/DMSans-Light.ttf"),
    [FontFamily["DM Sans-Regular"]]: require("../assets/fonts/DMSans/DMSans-Regular.ttf"),
    [FontFamily["DM Sans-Medium"]]: require("../assets/fonts/DMSans/DMSans-Medium.ttf"),
    [FontFamily["DM Sans-Bold"]]: require("../assets/fonts/DMSans/DMSans-Bold.ttf"),
    [FontFamily["DM Sans-Italic"]]: require("../assets/fonts/DMSans/DMSans-Italic.ttf"),
  });

  useTrackDeviceLocation();

  useEffect(() => {
    if (error) console.warn(error);
  }, [error]);

  useEffect(() => {
    if (loaded) {
      SplashScreen.hideAsync();
    }
  }, [loaded]);

  if (!loaded) {
    return null;
  }

  return <RootLayoutNav />;
};

export default function RootLayoutWithContext() {
  return (
    <RegisterContextProviders>
      {Constants.appOwnership !== "expo" && <ShareIntentComponent />}
      <RootLayout />
    </RegisterContextProviders>
  );
}

function RootLayoutNav() {
  useAuthentication();
  return (
    <AppContainer>
      <HomeLoadingScreen />
      <Stack
        screenOptions={{
          headerShown: false,
          navigationBarHidden: true,
        }}
        initialRouteName="index"
      >
        <Stack.Screen name="index" />
      </Stack>
    </AppContainer>
  );
}

###However in trying this, I am getting the error stated above, and never actually reaching the console.log() statements in my RootLayoutWithContext function

import React, { useEffect } from "react";
import { Platform, Text } from "react-native";
import { useFonts } from "expo-font";
import { SplashScreen, Stack, router } from "expo-router";
import Constants from "expo-constants";
import AppContainer from "../components/AppContainer/AppContainer";
import { RegisterContextProviders } from "../components/RegisterContextProviders/RegisterContextProviders";
import { useTrackDeviceLocation } from "../hooks/useTrackDeviceLocation";
import { FontFamily } from "styles";
import { useAuthentication } from "@hooks/useAuthentication";
import { HomeLoadingScreen } from "@components/Loading/HomeLoadingScreen";
import { ShareIntentProvider } from "expo-share-intent";

export { ErrorBoundary } from "expo-router";

export const unstable_settings = {
  initialRouteName: "(tabs)",
};

if (Platform.OS !== "web") SplashScreen.preventAutoHideAsync();

const RootLayout = () => {
  const [loaded, error] = useFonts({
    [FontFamily["DM Sans-Light"]]: require("../assets/fonts/DMSans/DMSans-Light.ttf"),
    [FontFamily["DM Sans-Regular"]]: require("../assets/fonts/DMSans/DMSans-Regular.ttf"),
    [FontFamily["DM Sans-Medium"]]: require("../assets/fonts/DMSans/DMSans-Medium.ttf"),
    [FontFamily["DM Sans-Bold"]]: require("../assets/fonts/DMSans/DMSans-Bold.ttf"),
    [FontFamily["DM Sans-Italic"]]: require("../assets/fonts/DMSans/DMSans-Italic.ttf"),
  });

  useTrackDeviceLocation();

  useEffect(() => {
    if (error) console.warn(error);
  }, [error]);

  useEffect(() => {
    if (loaded) {
      SplashScreen.hideAsync();
    }
  }, [loaded]);

  if (!loaded) {
    return <Text>Loading Fonts...</Text>; // Provides feedback during loading
  }

  return <RootLayoutNav />;
};

export default function RootLayoutWithContext() {
  // Detect if running in an environment without support for native modules
  const isExpoGo = Constants.appOwnership === "expo";
  console.log("Is Expo Go:", isExpoGo);
  console.log("Provider Disabled:", isExpoGo);

  return (
    <ShareIntentProvider
      options={{
        disabled: true, // Disable in unsupported environments like Expo Go
        debug: true,
        resetOnBackground: true,
        onResetShareIntent: () => {
          router.replace({
            pathname: "/",
          });
        },
      }}
    >
      <RegisterContextProviders>
        <RootLayout />
      </RegisterContextProviders>
    </ShareIntentProvider>
  );
}

function RootLayoutNav() {
  useAuthentication();
  return (
    <AppContainer>
      <HomeLoadingScreen />
      <Stack
        screenOptions={{
          headerShown: false,
          navigationBarHidden: true,
        }}
        initialRouteName="index"
      >
        <Stack.Screen name="index" />
      </Stack>
    </AppContainer>
  );
}

Environment

System:
  OS: macOS 14.3
  CPU: (10) arm64 Apple M1 Max
  Memory: 2.40 GB / 64.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 18.17.1
    path: ~/.nvm/versions/node/v18.17.1/bin/node
  Yarn:
    version: 1.22.19
    path: ~/.nvm/versions/node/v16.17.0/bin/yarn
  npm:
    version: 9.6.7
    path: ~/.nvm/versions/node/v18.17.1/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.15.2
    path: /usr/local/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.4
      - iOS 17.4
      - macOS 14.4
      - tvOS 17.4
      - visionOS 1.1
      - watchOS 10.4
  Android SDK: Not Found
IDEs:
  Android Studio: 2022.2 AI-222.4459.24.2221.10121639
  Xcode:
    version: 15.3/15E204a
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 18.0.1
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.73.6
    wanted: 0.73.6
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: true
  newArchEnabled: false
  • expo version (ex: 49) : 50
  • using firebase : yes, js sdk
  • using static build : not sure
  • routing : expo-router
  • package version (ex: 1.2.0) : 1.5.2
  • platform target : iOS
  • device : both

🤔
When you use the provider with disabled option, do you call the hook useShareIntentContext ? i didn't see it in your example (Should not use useShareIntent with provider). Or it just crash with the provider only ?

@achorein I do not call the useShareIntentContext when using the provider you created, before I even get that far the project will fail. The console log statements in my RootLayoutWithContext() inside the _layout.tsx file are not even getting triggered in expo go because it fails to resolve the import, I believe. Not sure how to get around that, even after following your specifications.

Right now my solution is to instead use the useShareIntent hook, which is imported in the require statement inside of a conditionally rendered component inside of my _layout.tsx file.

I know I sent large code blocks that are difficult to parse, so lmk if you need me to consolidate or expand more. Thank you!

@achorein would like to bump you on this! I know you have a life haha

I'm just curious why I can't use the package in the project without it failing unless if I do this, do you have any direction? :)

necessary structure of the component in order to make it compile

import { encodeWaypointForQueryString } from "@utils/map.utils";
import { trpc } from "@utils/trpc.utils";
import { router } from "expo-router";
import { useEffect, useState } from "react";

function parseShareIntentText(text: string) {
  // Splitting the text into place name and URL
  const splitIndex = text.indexOf("https://");
  if (splitIndex === -1) {
    console.error("No URL found in the text.");
    return null;
  }

  const placeName = text.substring(0, splitIndex).trim();
  const url = text.substring(splitIndex).trim();

  const urlObj = new URL(url);
  const llParam = urlObj.searchParams.get("ll");

  if (!llParam) {
    console.error("Latitude and longitude not found in the URL.");
    return null;
  }

  const [lat, lng] = llParam.split(",").map(Number);
  return {
    placeName,
    latitude: lat,
    longitude: lng
  };
}

// Separate component to handle share intent
export const ShareIntent = () => {
  const { useShareIntent } = require("expo-share-intent");
  const { hasShareIntent, shareIntent, resetShareIntent } = useShareIntent();
  const [parsedData, setParsedData] = useState<{ placeName: string; latitude: number; longitude: number } | null>(null);

  const { data } = trpc.routing.getPlaceInfoFromLatLng.useQuery(
    {
      lat: parsedData?.latitude ?? 0,
      lng: parsedData?.longitude ?? 0,
      radius: 100,
      keyword: parsedData?.placeName
    },
    { enabled: !!parsedData }
  );
  useEffect(() => {
    if (hasShareIntent) {
      const parsedIntentData = parseShareIntentText(shareIntent.text);
      if (parsedIntentData) {
        setParsedData(parsedIntentData);
      }
      resetShareIntent();
    }
  }, [hasShareIntent, shareIntent, resetShareIntent]);

  useEffect(() => {
    if (!data) return;
    const placeId = data[0].place_id ?? null;

    if (placeId) {
      const encodedWaypoint = encodeWaypointForQueryString({ id: placeId, t: "p" });
      router.navigate(`/launchpad/map/place/${encodedWaypoint}`);
    }
  }, [data]);

  return null;
};

importing it into my layout file

      {Constants.appOwnership !== "expo" && Platform.OS !== "web" && <ShareIntent />}

@cback97 calling import or require on "expo-share-intent" implies to call this line, which loads the native module on the android or ios side.
is the same problem here : #80
I need to handle the dynamic load in the package to properly manage the disabled option

Got ya!

@cback97 can you give a try on v2.2.0 should fixes the problem without the require hack.