stripe / stripe-terminal-android

Stripe Terminal Android SDK
https://stripe.dev/stripe-terminal-android/
Other
91 stars 45 forks source link

TTP crash in project which is using Firebase API #439

Closed kz-hcp closed 6 months ago

kz-hcp commented 7 months ago

Summary

Hello, I have a problem related to TTP integration in project which is using Firebase API.

During the discovery phase of the local reader I was getting this crash:

FATAL EXCEPTION: main
Process: com.stripe.cots.aidlservice, PID: 18600
java.lang.ExceptionInInitializerError
    at housecall.pros.app.initializers.CrashlyticsInitializer.initialize(CrashlyticsInitializer.kt:16)
    at housecall.pros.app.HouseCallApplication.onCreate(HouseCallApplication.kt:43)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1266)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7619)
    at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2400)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8762)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
Caused by: java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.stripe.cots.aidlservice. Make sure to call FirebaseApp.initializeApp(Context) first.
    at com.google.firebase.FirebaseApp.getInstance(FirebaseApp.java:179)
    at com.google.firebase.crashlytics.FirebaseCrashlytics.getInstance(FirebaseCrashlytics.java:207)
    at housecall.logger.CrashlyticsInitializer.<clinit>(CrashlyticsInitializer.kt:13)
    at housecall.pros.app.initializers.CrashlyticsInitializer.initialize(CrashlyticsInitializer.kt:16) 
    at housecall.pros.app.HouseCallApplication.onCreate(HouseCallApplication.kt:43) 
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1266) 
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7619) 
    at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2400) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loopOnce(Looper.java:226) 
    at android.os.Looper.loop(Looper.java:313) 
    at android.app.ActivityThread.main(ActivityThread.java:8762) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067) 
FATAL EXCEPTION: main
Process: com.stripe.cots.aidlservice, PID: 18627
java.lang.ExceptionInInitializerError
    at housecall.pros.app.initializers.CrashlyticsInitializer.initialize(CrashlyticsInitializer.kt:16)
    at housecall.pros.app.HouseCallApplication.onCreate(HouseCallApplication.kt:43)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1266)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7619)
    at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2400)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8762)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
Caused by: java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.stripe.cots.aidlservice. Make sure to call FirebaseApp.initializeApp(Context) first.
    at com.google.firebase.FirebaseApp.getInstance(FirebaseApp.java:179)
    at com.google.firebase.crashlytics.FirebaseCrashlytics.getInstance(FirebaseCrashlytics.java:207)
    at housecall.logger.CrashlyticsInitializer.<clinit>(CrashlyticsInitializer.kt:13)
    at housecall.pros.app.initializers.CrashlyticsInitializer.initialize(CrashlyticsInitializer.kt:16) 
    at housecall.pros.app.HouseCallApplication.onCreate(HouseCallApplication.kt:43) 
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1266) 
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7619) 
    at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2400) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loopOnce(Looper.java:226) 
    at android.os.Looper.loop(Looper.java:313) 
    at android.app.ActivityThread.main(ActivityThread.java:8762) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067) 

Followed by:

class=AidlRpcClient message="AIDL RPC error"
com.stripe.core.aidlrpc.AidlRpcException: Service never connected
    at com.stripe.core.aidlrpcclient.AidlRpcClient.onAidlError(AidlRpcClient.kt:136)
    at com.stripe.core.aidlrpcclient.AidlRpcClient.bindToService(AidlRpcClient.kt:63)
    at com.stripe.stripeterminal.internal.common.adapter.CotsClient$bindIfNeeded$2.invokeSuspend(CotsClient.kt:78)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 60000 ms
    at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:191)
    at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:159)
    at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:501)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280)
    at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:109)
    at java.lang.Thread.run(Thread.java:1012)
class=TerminalSession
com.stripe.stripeterminal.external.models.TerminalException: Contactless transaction failed com.stripe.core.aidlrpc.AidlRpcException: Service never connected.
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.callAidlWithExceptionConverted(CotsAdapter.kt:369)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.access$callAidlWithExceptionConverted(CotsAdapter.kt:58)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter$DiscoverReadersOperation.execute(CotsAdapter.kt:380)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter$DiscoverReadersOperation.execute(CotsAdapter.kt:377)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.discoverReaders(CotsAdapter.kt:266)
    at com.stripe.stripeterminal.internal.common.adapter.ProxyAdapter.discoverReaders(ProxyAdapter.kt:205)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$DiscoverReadersOperation.executeIfNotCanceled(TerminalSession.kt:1801)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$CancelableOperation.execute(TerminalSession.kt:1021)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$ExternalOperation.run$terminalsession_release(TerminalSession.kt:979)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.enqueueOperation$lambda$6(TerminalSession.kt:838)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.$r8$lambda$6lM7341_XI6PEqXfOM-yc2U5tkQ(Unknown Source:0)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$$ExternalSyntheticLambda0.run(Unknown Source:4)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
    at java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
    at java.lang.Thread.run(Thread.java:1012)
failure when connecting readers
com.stripe.stripeterminal.external.models.TerminalException: Contactless transaction failed com.stripe.core.aidlrpc.AidlRpcException: Service never connected.
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.callAidlWithExceptionConverted(CotsAdapter.kt:369)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.access$callAidlWithExceptionConverted(CotsAdapter.kt:58)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter$DiscoverReadersOperation.execute(CotsAdapter.kt:380)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter$DiscoverReadersOperation.execute(CotsAdapter.kt:377)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.discoverReaders(CotsAdapter.kt:266)
    at com.stripe.stripeterminal.internal.common.adapter.ProxyAdapter.discoverReaders(ProxyAdapter.kt:205)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$DiscoverReadersOperation.executeIfNotCanceled(TerminalSession.kt:1801)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$CancelableOperation.execute(TerminalSession.kt:1021)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$ExternalOperation.run$terminalsession_release(TerminalSession.kt:979)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.enqueueOperation$lambda$6(TerminalSession.kt:838)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.$r8$lambda$6lM7341_XI6PEqXfOM-yc2U5tkQ(Unknown Source:0)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$$ExternalSyntheticLambda0.run(Unknown Source:4)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
    at java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
    at java.lang.Thread.run(Thread.java:1012)

I have noticed this crash was similar to the one described in issue #323 where a workaround of early returning from the Application's onCreate has been proposed.

After implementing this workaround all was working well (I was able to connect and take payments with TTP) but only on debug builds of the app (which had Firebase Performance Monitoring disabled).

On release builds (which had Firebase Performance Monitoring enabled) I started getting another, more enigmatic error, this time during connection phase.

First attempt of connection would give me this error:

class=TerminalSession
com.stripe.stripeterminal.external.models.TerminalException: Unexpected error occurred
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.checkAndThrowCotsError(SourceFile:84)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.onReaderActivated(SourceFile:61)
    at com.stripe.stripeterminal.internal.common.adapter.ProxyAdapter.onReaderActivated(SourceFile:15)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.activateReader$terminalsession_release(SourceFile:83)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$ConnectReaderOperation.execute(SourceFile:191)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$ExternalOperation.run$terminalsession_release(SourceFile:17)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.enqueueOperation$lambda$6(SourceFile:52)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.a(SourceFile:1)
    at i.s.run(SourceFile:14)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
    at java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
    at java.lang.Thread.run(Thread.java:1012)

TerminalErrorCode is UNEXPECTED_ERROR.UNEXPECTED_SDK_ERROR.

Any subsequent connection attempt would return this error:

class=TerminalSession
com.stripe.stripeterminal.external.models.TerminalException: com.google.firebase.perf.config.RemoteConfigManager
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.checkAndThrowCotsError(SourceFile:84)
    at com.stripe.stripeterminal.internal.common.adapter.CotsAdapter.onReaderActivated(SourceFile:61)
    at com.stripe.stripeterminal.internal.common.adapter.ProxyAdapter.onReaderActivated(SourceFile:15)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.activateReader$terminalsession_release(SourceFile:83)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$ConnectReaderOperation.execute(SourceFile:191)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession$ExternalOperation.run$terminalsession_release(SourceFile:17)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.enqueueOperation$lambda$6(SourceFile:53)
    at com.stripe.stripeterminal.internal.common.terminalsession.TerminalSession.a(SourceFile:1)
    at com.stripe.stripeterminal.internal.common.terminalsession.a.run(SourceFile:1)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
    at java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
    at java.lang.Thread.run(Thread.java:1012)

I started to look for a solution and I looped back to the original error from before a workaround which said:

Default FirebaseApp is not initialized in this process com.stripe.cots.aidlservice. Make sure to call FirebaseApp.initializeApp(Context) first.

I have noticed our app didn't call the FirebaseApp.initializeApp(Context) anywhere (which is fine according to FirebaseApp docs) so I've decided to add it to our Application's onCreate:

import com.google.firebase.Firebase
import com.google.firebase.initialize

    override fun onCreate() {
        super.onCreate()
        Firebase.initialize(this)
        ...
    }

With this change all of the errors are gone. TTP works well on both debug and release versions of the app.

What I would like to confirm is: is this the proper solution?

I think this solution goes against the proposed workaround which said we should initialise Firebase only on main process of the app so I'm a little bit confused here.

Also if you take a look at FirebaseApp.initializeApp documentation it says:

Initializes the default FirebaseApp instance using string resource values - populated from google-services.json. It also initializes Firebase Analytics for the current process.

This method is called at app startup time by FirebaseInitProvider. Call this method before any Firebase APIs in components outside the main process.

Doesn't this mean that when "outside of the main process" we should call FirebaseApp.initializeApp?

If I'm understanding that correctly it would mean the proposed workaround is not valid.

Please advise 🙏

Android version

Android 13

Impacted devices (Android devices or readers)

Samsung Galaxy A52 (SM-A525F/DS).

SDK version

com.stripe:stripeterminal-core:2.23.2
com.stripe:stripeterminal-localmobile:2.23.2
com.google.firebase:firebase-bom:32.4.0
ugochukwu-stripe commented 7 months ago

Hi @kz-hcp, we've reached out to the Firebase Android SDK team ^1 to clarify seemingly conflicting messaging in their documentation.

ugochukwu-stripe commented 6 months ago

Closing this out, although not explicitly stated, your approach works. Please refer to linked firebase issue for further information