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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions