Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
skam22 opened this issue Sep 21, 2023 · 6 comments
Labels
bug Something isn't working

Comments

@skam22
Copy link

skam22 commented Sep 21, 2023

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 skam22 added the bug Something isn't working label Sep 21, 2023
@RCGitBot
Copy link
Contributor

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

@mshmoustafa
Copy link
Contributor

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

@NachoSoto
Copy link
Contributor

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?

@Cnordbo
Copy link

Cnordbo commented Oct 10, 2023

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

@vegaro
Copy link
Contributor

vegaro commented Oct 18, 2023

@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?

@Cnordbo
Copy link

Cnordbo commented Oct 27, 2023

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants