react-native-purchases icon indicating copy to clipboard operation
react-native-purchases copied to clipboard

Purchases.syncPurchases error: "There is no singleton instance. Make sure you configure Purchases before trying to get the default instance."

Open skam22 opened this issue 11 months ago • 6 comments

Describe the bug

Purchases.syncPurchases() Error:

 WARN  Possible Unhandled Promise Rejection (id: 0):
Error: There is no singleton instance. Make sure you configure Purchases before trying to get the default instance. More info here: https://errors.rev.cat/configuring-sdk
  1. Environment
    1. Platform: iOS and Android
    2. SDK version: 6.6.5
    3. OS version:
    4. Xcode/Android Studio version:
    5. React Native version: 0.72.4
    6. SDK installation (CocoaPods + version or manual):
    7. How widespread is the issue. Percentage of devices affected. 100%
  2. Debug logs that reproduce the issue
  3. Steps to reproduce, with a description of expected vs. actual behavior

index.js:

import Purchases from 'react-native-purchases';
Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG);
Purchases.configure({apiKey: xxxxxxxxxxxxxxxx});

App.tsx


const storage = new MMKV();

const App = () => {
  useEffect(() => {
    const init = async () => {
      try {
        if (!storage.getBoolean(MMKVKeys.HAS_SYNCED_PURCHASES)) {
          const configured = await Purchases.isConfigured();
          console.log('revenue cat is configured:', configured);  // this console.log shows "revenue cat is configured: true"

          if (configured) {
            await Purchases.syncPurchases();
            storage.set(MMKVKeys.HAS_SYNCED_PURCHASES, true);
            console.log('mmkv stored:', storage.getBoolean(MMKVKeys.HAS_SYNCED_PURCHASES)); // this console.log shows "mmkv stored: true"
            console.log('revenue cat purchases synced') // this console.log is printed successfully;
          };
        };
      } catch (error) {
        console.log('caught error', error)
      }      
    };
    init();
  },[])
};
export default App;

interestingly syncPurchases resolves the promise successfully so the error is not caught by the try/catch block. an unhandled promise warning is thrown AFTER syncPurchases() has supposedly already completed.

  1. Other information (e.g. stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, etc.)
 LOG  Platform: ios 16.6.1 Mode: debug
 LOG  Running "SAMPLE" with {"rootTag":1,"initialProps":{}}
 LOG  revenue cat is configured: true
 LOG  mmkv stored: true
 LOG  revenue cat purchases synced
 
 WARN  Possible Unhandled Promise Rejection (id: 0):
Error: There is no singleton instance. Make sure you configure Purchases before trying to get the default instance. More info here: https://errors.rev.cat/configuring-sdk
Error: There is no singleton instance. Make sure you configure Purchases before trying to get the default instance. More info here: https://errors.rev.cat/configuring-sdk
    at call (native)
    at UninitializedPurchasesError (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:137882:30)
    at anonymous (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:137668:132)
    at call (native)
    at step (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:136259:23)
    at anonymous (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:136208:20)
    at fulfilled (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:136167:30)
    at tryCallOne (/Users/distiller/react-native/packages/react-native/sdks/hermes/build_iphoneos/lib/InternalBytecode/InternalBytecode.js:53:16)
    at anonymous (/Users/distiller/react-native/packages/react-native/sdks/hermes/build_iphoneos/lib/InternalBytecode/InternalBytecode.js:139:27)
    at apply (native)
    at anonymous (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:34672:26)
    at _callTimer (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:34551:17)
    at _callReactNativeMicrotasksPass (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:34596:17)
    at callReactNativeMicrotasks (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:34802:44)
    at __callReactNativeMicrotasks (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:3505:46)
    at anonymous (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:3279:45)
    at __guard (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:3478:15)
    at flushedQueue (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:3278:21)
    at invokeCallbackAndReturnFlushedQueue (http://192.168.0.100:8081/index.bundle//&platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.sample:3272:33)

skam22 avatar Sep 21 '23 20:09 skam22

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

RCGitBot avatar Sep 21 '23 20:09 RCGitBot

Hello, I'll get some input from our engineers here and get back to you soon.

mshmoustafa avatar Sep 25 '23 22:09 mshmoustafa

Hi @skam22, I've been looking into this and haven't been able to find what could possibly be wrong here. Something did catch my attention though:

interestingly syncPurchases resolves the promise successfully so the error is not caught by the try/catch block. an unhandled promise warning is thrown AFTER syncPurchases() has supposedly already completed.

Is it possible that the error is being thrown from a different call to syncPurchases?

NachoSoto avatar Oct 09 '23 16:10 NachoSoto

Hi, Could this be due to React rendering components twice in Development mode?

I believe i noticed that Purchases.Configure() actually triggers a promise rejection if called twice. Even if it says its not returning any promises. So i put Purchases.isConfigured() as a guard around the Configure - which got rid of the Promise rejection.

Looking at the code, it seems that Purchases.Configure() is a more or less direct method against the RCT_EXPORT_METHOD - Which is an async operation that always returns void . But the async operation probably gets rejected because of an internal error in the iOS code.

Ref: React Docs - Keeping Components Pure

Cnordbo avatar Oct 10 '23 21:10 Cnordbo

@Cnordbo that's definitely interesting. I think this could happen if the components are being rendered twice. I don't see code in the Android SDK that will trigger an exception if configure is called twice. But in iOS I see that it will throw an exception when in DEBUG

@skam22 are you setting strict mode like indicated in React Docs - Keeping Components Pure? Do you think this could be causing the issues you're seeing?

vegaro avatar Oct 18 '23 12:10 vegaro

@vegaro - I am not sure about this, but let me just assume something as well here.

You dont really need to run in strict mode to trigger this, i think, as i am not running StrictMode myself and got this. I would assume (but could be wrong here) that the instance generated in iOS (and probably Android as well) is the same during re-renders.

So while your developing, you would most-likely cause a re-render each time of the affected components. Meaning that if you change something that, depending on your implementation / .Configure point, requires a re-render of your .Configure / Complete re-render, meaning this would get called twice anyway, while still keeping the runtime-instance of Purchases alive.

As a change, would it be possible to allow the .Configure method be called multiple times without throwing an exception, if it really doesnt matter? I do get why it could be a good idea to give some sort of feedback that its beeing called twice, when its only ment to be called once. But does it really affect anything if called twice?

Cnordbo avatar Oct 27 '23 13:10 Cnordbo