twilio / twilio-voice-react-native

Other
71 stars 26 forks source link

Error during call init via Expo ([TypeError: Cannot read property 'voice_connect_android' of null]) #292

Closed dmitriy-uvin closed 7 months ago

dmitriy-uvin commented 7 months ago

Issue

Pre-submission Checklist

Description

I need to implement the ability to make and receive calls from my React Native app. I use React Native in conjunction with Expo. I used https://github.com/twilio/twilio-voice-react-native-app as an example, but it for raw react-native app, not Expo. When I am trying to init the call from my RN app I receive an error described below.

Expected Behavior

Instead of error I need to start the call with callee.

Actual Behavior

Instead of creating call and calling to callee - error.

Reproduction Frequency

Try to connect to the call in Expo React Native project.

Software and Device Information

Please complete the following information.

Additional Context

I need to implement ability to make and receive calls from my React Native app. I use React Native in conjunction with Expo. Here is my package.json

{
  "name": "dialer",
  "main": "expo-router/entry",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "test": "jest --watchAll"
  },
  "jest": {
    "preset": "jest-expo"
  },
  "dependencies": {
    "@expo/vector-icons": "^14.0.0",
    "@react-native-async-storage/async-storage": "1.21.0",
    "@react-native-picker/picker": "2.6.1",
    "@react-navigation/native": "^6.0.2",
    "@twilio/voice-react-native-sdk": "^1.0.0-beta.4",
    "@twilio/voice-sdk": "^2.10.1",
    "axios": "^1.6.5",
    "dayjs": "^1.11.10",
    "expo": "~50.0.1",
    "expo-av": "~13.10.4",
    "expo-font": "~11.10.2",
    "expo-linking": "~6.2.2",
    "expo-router": "~3.4.3",
    "expo-splash-screen": "~0.26.3",
    "expo-status-bar": "~1.11.1",
    "expo-system-ui": "~2.9.3",
    "expo-web-browser": "~12.8.1",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-hook-form": "^7.49.3",
    "react-native": "0.73.2",
    "react-native-bouncy-checkbox": "^3.0.7",
    "react-native-dotenv": "^3.4.9",
    "react-native-safe-area-context": "4.8.2",
    "react-native-screens": "~3.29.0",
    "react-native-toast-notifications": "^3.4.0",
    "react-native-web": "~0.19.6",
    "twilio": "^4.21.0"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@types/axios": "^0.14.0",
    "@types/react": "~18.2.45",
    "jest": "^29.2.1",
    "jest-expo": "~50.0.1",
    "react-test-renderer": "18.2.0",
    "typescript": "^5.1.3"
  },
  "private": true
}

Here is my API that is responsible for generating Access Token with Voice grant:

public async getAccessTokenForCall(userUuid: string): Promise<string> {
    const twilio = require('twilio');
    const AccessToken = twilio.jwt.AccessToken;
    const VoiceGrant = AccessToken.VoiceGrant;
    const voiceGrant = new VoiceGrant({
      outgoingApplicationSid: '<TwilML application SID here>',
      incomingAllow: true,
    });
    const token = new AccessToken(
      this.accountSid,
      this.apiKey,
      this.apiKeySecret,
      {
        identity: userUuid.replaceAll('-', '_'),
      },
    );
    token.addGrant(voiceGrant);
    const jwtValue = token.toJwt();
    return jwtValue;
  }

Here is my method that is responsible for TwilML that is used in TwilML app:

@Post('/ml')
  @Header('Content-Type', 'text/plain')
  public async twilMl(@Req() req: AppRequest) {
    const response = new twiml.VoiceResponse();
    const dial = response.dial({
      answerOnBridge: true,
    });
    dial['number']('+<hardcoded phone number for testing>')
    return response.toString();
  }

React native component that triggers call:

try {
        load();
        const tokenRes: AccessTokenDto = await twilioApi.getVoiceToken();
        const token = tokenRes.accessToken;
        const voice: Voice = new Voice();
        voice.on(Voice.Event.Registered, (data: any) => {
          console.log('registered', data);
        });
        voice.on(Voice.Event.Error, (data) => {
          console.log('voice error', data);
        })
        const call = await voice.connect(token, {
          params: {
            To: phoneNumber,
            recipientType: 'number',
          }
        });
      } catch (e) {
        console.log(e);
      } finally {
        loadEnd();
      }

Important note: event voice.on are not triggered at all.

I receive next error during call init:

[TypeError: Cannot read property 'voice_connect_android' of null]

ANY IDEAS how it can be solved?

mhuynh5757 commented 7 months ago

Hello @dmitriy-uvin I'm going to link this issue here, I am assuming it is the same issue, please let me know if that is not the case.

https://github.com/twilio/twilio-voice-react-native-app/issues/136

I am also going to close the issue on the other repository.

I suspect that your issue could be caused by Expo. We unfortunately do not support Expo, we have not tested our library in that environment and it is unfortunately not in our roadmap at this time. Could you please try running the Reference App on your device to see if you encounter the same issue?

dmitriy-uvin commented 7 months ago

Hi @mhuynh5757. The reference app with struggles but working, but it's really hard to implement it in the new RN app that uses Kotlin instead of Java without proper documentation. What about iOS platform, is it ready-to-go for ios? How it can be handled?

dmitriy-uvin commented 7 months ago

Here is the link to my startup app - https://github.com/dmitriy-uvin/mobile-dialpad. And issue that extremely blocks me - https://github.com/twilio/twilio-voice-react-native/issues/298.

mhuynh5757 commented 7 months ago

Hi @dmitriy-uvin yes the library and reference app are both iOS ready. I have responded to your other issue, please refer to my comment there. I will discuss with the team regarding Kotlin.

dmitriy-uvin commented 7 months ago

@mhuynh5757 Responded in other issues, thank you. If you can it would be better to discuss creating better documentation (part that is related to android & ios folders). Kotlin is pretty similar to Java, so it's easy to convert Java code to Kotlin (at least for now).

shridhar-toptal commented 7 months ago

@dmitriy-uvin Library works fine on both Android and iOS with Expo EAS. Just needs some manual changes for incoming calls in Android, but outgoing works well.

dmitriy-uvin commented 7 months ago

@shridhar-toptal where can I find what changes needs to be done in order to enable it in expo?

shridhar-toptal commented 7 months ago

@dmitriy-uvin Here is the plugin I created for Expo

const { withPlugins, withAndroidManifest } = require("@expo/config-plugins");

function withTwilioAndroidFix(config) {
  return withAndroidManifest(config, (mod) => {
    mod.modResults.manifest.application[0].service = [
      ...(mod.modResults.manifest.application[0].service ?? []),
      {
        $: {
          "android:name":
            "com.twiliovoicereactnative.VoiceFirebaseMessagingService",
          "android:stopWithTask": "false",
          "android:exported": "true",
        },
        "intent-filter": [
          {
            action: [
              {
                $: {
                  "android:name": "com.google.firebase.MESSAGING_EVENT",
                },
              },
            ],
          },
        ],
      },
      {
        $: {
          "android:enabled": "true",
          "android:name":
            "com.twiliovoicereactnative.IncomingCallNotificationService",
          "android:foregroundServiceType": "microphone",
          "android:exported": "true",
        },
        "intent-filter": [
          {
            action: [{ $: { "android:name": "ACTION_ACCEPT" } }],
          },
          {
            action: [{ $: { "android:name": "ACTION_REJECT" } }],
          },
        ],
      },
    ];

    mod.modResults.manifest.application[0].activity = [
      ...(mod.modResults.manifest.application[0].activity ?? []),
      {
        $: {
          "android:name":
            "com.twiliovoicereactnative.NotificationProxyActivity",
          "android:parentActivityName": ".VoiceActivity",
          "android:noHistory": "true",
          "android:excludeFromRecents": "true",
          "android:taskAffinity": "",
          "android:launchMode": "singleTask",
          "android:theme": "@android:style/Theme.Translucent.NoTitleBar",
          "android:exported": "true",
        },
      },
    ];

    return mod;
  });
}

module.exports = function (config) {
  return withPlugins(config, [withTwilioAndroidFix]);
};
dmitriy-uvin commented 7 months ago

@shridhar-toptal do you have any documentation link for it?