Skip to main content
Question

BillingWrapper is not attached to a listener after relogin

  • 8 August 2024
  • 8 replies
  • 78 views

Hi team. Found one problem. When I’ve tried to relogin to the same account - always receive the problem with `getProducts` method: 

```

BillingWrapper is not attached to a listener

```

Precondition:

  • Setup global customerListener
    ```
    Purchases.sharedInstance.updatedCustomerInfoListener =    UpdatedCustomerInfoListener { customerInfo →       if(customerInfo.originalAppUserId == myCurrentUserId) {         fetchProducts(customerInfo)       }    }
    ```

Found stable steps to reproduce:

  1. Login to account A (`Purchases.sharedInstance.awaitLogIn(firebaseUser.uid)`)
  2. Logout from account A
  3. Login to account B
  4. Logout from account B
  5. Login to account A, await `customerInfo`, call getProducts
    Result:
    ```
    PurchasesError(code=UnknownError, underlyingErrorMessage=BillingWrapper is not attached to a listener, message='Unknown error.')
    ```

Hey @ipavlovskii,

Can you share the entire debug logs while doing those steps? I’ll pass them to our mobile engineering team to take a look.


Put that code here:

 

https://github.com/RevenueCat/purchases-android/issues/1765#issuecomment-2285905576

 

```

fun authState(): Flow<FirebaseUser?> {
val firebaseAuth = FirebaseAuth.getInstance()
return callbackFlow {
val callback = FirebaseAuth.AuthStateListener { firebaseAuth ->
this.trySendBlocking(firebaseAuth.currentUser)
}
firebaseAuth.addAuthStateListener(callback)
awaitClose {
firebaseAuth.removeAuthStateListener(callback)
}
}
}

fun syncrhonization() {
var syncJob: Job? = null
val coroutineScope: CoroutineScope = MainScope()
authState()
.collectLatest { firebaseUser ->
val purchases = Purchases.sharedInstance
if (firebaseUser != null) {
with(purchases) {
this.awaitCancellableLogIn(firebaseUser.uid)
this.setEmail(firebaseUser.email)
this.setDisplayName(firebaseUser.displayName)
this.setDisplayName(firebaseUser.displayName)
}
syncJob?.cancel()
syncJob = coroutineScope.async {
purchases.awaitCancellableRestore()
}

} else {
syncJob?.cancel()
if (!purchases.isAnonymous) {
purchases.awaitCancellableLogOut()
}
}
}.launchIn(coroutineScope)
}

```


@ipavlovskii can you share the debug logs? Code looks good but the full debug logs from beginning all the way until the error is reproduced is helpful for our engineering team to look into this


Hi @sharif , sorry for long reply. Probably, I found the answer:

 

By the documentation the single point of interaction with Revenucat purchases is `Purchases.sharedInstance`. By the doc - it’s a singleton, but… by the implementation - it’s not

 

That’s mean, if I initialize a Purchases and store in a private variable/DI/IoC/dependency provider - that might be Ndifferent instances:

```

val purchaseSingletonInstanceForMyApplication = Purchases.sharedInstance

//…. something doing and `Purchases.sharedInstance` reinitialized → set method calls → prev link to instance `purchaseSingletonInstanceForMyApplication`

purchaseSingletonInstanceForMyApplication - instance closed and any purchase call from this instance will failed

```

 

So, to fix that case you might:
1. update documentation

2. make it real as a real singleton, because `get()` method might return different instances
 


Hi @ipavlovskii,

I’m glad to hear you’ve fixed it! And yes, thanks for that feedback, I agree it can be clarified. I’ll pass it on to the SDK team.


I forgot to ask one question - are you calling Purchases.configure more than once? Just making sure you’re not running into issues caused by that. Purchases.configure should be called exactly once in an app’s lifecycle


No, I’m call configure only once on the app initialization

 

If I’m not wrong, handle that case after login
 


val purchase = Purchases.sharedInstance
val loginResult = purchases.awaitLogIn(firebaseUser.uid)
purchase.getProducts(someProductsIdsArray) // Crashed with BillingWrapper is not attached

 


Hi @ipavlovskii,

Thanks for confirming and sorry for the delay. Just wanted to close the loop on this, it looks like you’re configuring correctly so no action needed on that. I’ve passed the feedback on to the mobile engineering team. If anything else comes up in the future please feel free to reach back out!


Reply