Skip to content

Truecaller SDK fails with dexopt status error: Failed to create OatFileAssistant (pcam.jar) #65

@ayushambatkar

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
  1. Fresh install the app
  2. Open the app → Truecaller sign-in bottom sheet does not appear
  3. 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';
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions