-
Notifications
You must be signed in to change notification settings - Fork 49
Open
Description
When integrating truecaller_sdk, the sign-in bottom sheet sometimes fails to appear on the very first app launch after a fresh install.
The logcat shows a dexopt status error related to pcam.jar.
On the second app open onwards, the SDK works fine.
Reproducible Steps
Config:
- flutter: 3.32.8
- truecaller_sdk: ^1.0.1
- Fresh install the app
- Open the app → Truecaller sign-in bottom sheet does not appear
- Close and reopen the app → works normally
Expected Behavior
The Truecaller sign-in bottom sheet should appear consistently, even on the first app open after fresh install.
Actual Behavior
- On first app open → fails, bottom sheet doesn’t appear
- Logcat shows
Failed to get dexopt status … Failed to create OatFileAssistant (pcam.jar)error - On second app open → works as expected
Error Log
Click to expand
2025-09-15 19:06:09.031 2120-4135 ArtService pid-2120 E Failed to get dexopt status [packageName = com.truecaller, dexPath = /data/user/0/com.truecaller/app_pccache/5/93C3DCFD0867095FE4406F170E27CC60B68F10CD/pcam.jar, isa = arm64, classLoaderContext = PCL[];PCL[/data/app/~~x0s3XsgbMN1L1S0ktR-XjQ==/com.truecaller-WmcK-MI5qdv-Jj2fi_nJ2w==/base.apk:/data/app/~~x0s3XsgbMN1L1S0ktR-XjQ==/com.truecaller-WmcK-MI5qdv-Jj2fi_nJ2w==/split_insights_category_model.apk]{PCL[/system/framework/org.apache.http.legacy.jar]#PCL[/system_ext/framework/androidx.window.extensions.jar]#PCL[/system_ext/framework/androidx.window.sidecar.jar]}] (Ask Gemini)
android.os.ServiceSpecificException: Failed to create OatFileAssistant: Failed to load class loader context files for '/data/user/0/com.truecaller/app_pccache/5/93C3DCFD0867095FE4406F170E27CC60B68F10CD/pcam.jar' with context 'PCL[];PCL[/data/app/~~x0s3XsgbMN1L1S0ktR-XjQ==/com.truecaller-WmcK-MI5qdv-Jj2fi_nJ2w==/base.apk:/data/app/~~x0s3XsgbMN1L1S0ktR-XjQ==/com.truecaller-WmcK-MI5qdv-Jj2fi_nJ2w==/split_insights_category_model.apk]{PCL[/system/framework/org.apache.http.legacy.jar]#PCL[/system_ext/framework/androidx.window.extensions.jar]#PCL[/system_ext/framework/androidx.window.sidecar.jar]}' (code 1)
at android.os.Parcel.createExceptionOrNull(Parcel.java:3271)
at android.os.Parcel.createException(Parcel.java:3241)
at android.os.Parcel.readException(Parcel.java:3224)
at android.os.Parcel.readException(Parcel.java:3166)
at com.android.server.art.IArtd$Stub$Proxy.getDexoptStatus(IArtd.java:880)
at com.android.server.art.ArtFileManager.getUsableArtifacts(ArtFileManager.java:153)
at com.android.server.art.ArtManagerLocal.getArtManagedFileStats(ArtManagerLocal.java:1056)
at com.android.server.usage.StorageStatsService.computeAppStatsByDataTypes(qb/99169682 11305582a0e7ecd158a7fcd710f0dc155550890710b748b94dfd39a1df857f62:75)
at com.android.server.usage.StorageStatsService.queryStatsForUid(qb/99169682 11305582a0e7ecd158a7fcd710f0dc155550890710b748b94dfd39a1df857f62:138)
at com.android.server.usage.StorageStatsService.queryStatsForPackage(qb/99169682 11305582a0e7ecd158a7fcd710f0dc155550890710b748b94dfd39a1df857f62:83)
at android.app.usage.IStorageStatsManager$Stub.onTransact(IStorageStatsManager.java:265)
at android.os.Binder.execTransactInternal(Binder.java:1536)
at android.os.Binder.execTransact(Binder.java:1480)
Dart Snippet
Click to expand
import 'dart:async';
import 'package:flicktv/domain/analytics/ga_events.dart';
import 'package:flicktv/domain/use_cases/record_error.dart';
import 'package:truecaller_sdk/truecaller_sdk.dart';
import 'package:uuid/uuid.dart';
mixin TruecallerUsecase {
String? _codeVerifier;
Stream<TcSdkCallback> get truecallerStream => TcSdk.streamCallbackData;
// Helper to ensure Truecaller is available on device
Future<void> _ensureTruecallerAvailable() async {
final usable = await TcSdk.isOAuthFlowUsable;
if (!usable) {
throw TruecallerUnavailableException();
}
}
Future<void> _initTruecaller() async {
TcSdk.initializeSDK(
sdkOption: TcSdkOptions.OPTION_VERIFY_ONLY_TC_USERS,
consentHeadingOption: TcSdkOptions.SDK_CONSENT_HEADING_LOG_IN_TO,
footerType: TcSdkOptions.FOOTER_TYPE_ANOTHER_MOBILE_NO,
ctaText: TcSdkOptions.CTA_TEXT_PROCEED,
buttonShapeOption: TcSdkOptions.BUTTON_SHAPE_ROUNDED,
);
// Do not auto-listen here; we will await the first event explicitly
}
Future<TcPayload?> initiateTruecallerLogin() async {
_initTruecaller();
await _ensureTruecallerAvailable();
flickAnalytics.logEvent(GAEvent.TC_LOGIN_INITIATED);
final oAuthState = Uuid().v4();
TcSdk.setOAuthState(oAuthState); // step 3.1
TcSdk.setOAuthScopes(['profile', 'phone', 'openid']); // step 3.2
_codeVerifier = await TcSdk.generateRandomCodeVerifier;
if (_codeVerifier == null) {
return null;
}
final codeChallenge = await TcSdk.generateCodeChallenge(_codeVerifier!);
if (codeChallenge != null) {
TcSdk.setCodeChallenge(codeChallenge);
TcSdk.getAuthorizationCode;
}
return await _getTruecallerPayload();
}
// Await the first callback for up to [timeout]. Return TcPayload on success, else null.
Future<TcPayload?> _getTruecallerPayload({
Duration postProceedTimeout = const Duration(seconds: 5),
}) async {
try {
// Phase 1: user can take any amount of time to press Proceed
final first = await truecallerStream.first;
final number = first.profile?.phoneNumber;
if (first.result == TcSdkCallbackResult.success) {
final data = first.tcOAuthData!;
final payload = TcPayload(
authCode: data.authorizationCode,
state: data.state,
codeVerifier: _codeVerifier!,
phoneNumber: number,
);
return payload;
}
if (first.result == TcSdkCallbackResult.verification) {
// Phase 2: after proceed/verification begins, wait for success with timeout
final success = await truecallerStream
.where((e) => e.result == TcSdkCallbackResult.success)
.first
.timeout(postProceedTimeout);
final data = success.tcOAuthData!;
final payload = TcPayload(
authCode: data.authorizationCode,
state: data.state,
codeVerifier: _codeVerifier!,
phoneNumber: number,
);
return payload;
}
// failure or any other case -> fallback
return null;
} on TruecallerUnavailableException {
// Propagate to caller so it can handle non-Truecaller devices explicitly
rethrow;
} on TimeoutException catch (e, stack) {
recordError(e, stack, reason: "Truecaller payload timeout");
return null;
} catch (e, st) {
recordError(e, st);
return null;
}
}
}
class TcPayload {
final String authCode;
final String state;
final String codeVerifier;
final String? phoneNumber;
TcPayload({
required this.authCode,
required this.state,
required this.codeVerifier,
this.phoneNumber,
});
@override
String toString() {
return 'TcPayload(authCode: $authCode, state: $state, codeVerifier: $codeVerifier, phoneNumber: $phoneNumber)';
}
}
// Specific exception to signal unavailability of Truecaller on device
class TruecallerUnavailableException implements Exception {
final String message;
TruecallerUnavailableException(
[this.message = 'Truecaller is not available on this device']);
@override
String toString() => 'TruecallerUnavailableException: $message';
}
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels