ionic-team / capacitor

Build cross-platform Native Progressive Web Apps for iOS, Android, and the Web ⚡️
https://capacitorjs.com
MIT License
11.5k stars 983 forks source link

bug: java.lang.NullPointerException in BridgeWebChromeClient.java #5284

Closed DontGiveAFck closed 2 years ago

DontGiveAFck commented 2 years ago

Bug Report

Capacitor Version

💊   Capacitor Doctor  💊 

Latest Dependencies:

  @capacitor/cli: 3.3.2
  @capacitor/core: 3.3.2
  @capacitor/android: 3.3.2
  @capacitor/ios: 3.3.2

Installed Dependencies:

  @capacitor/cli: 3.2.4
  @capacitor/core: 3.2.4
  @capacitor/android: 3.2.4
  @capacitor/ios: 3.2.4

[success] iOS looking great! 👌
[success] Android looking great! 👌

Platform(s)

Android - doesn't depend on version/device

Current Behavior

After releasing app to prod some users get error on 1-5s after app launch (according to Crashlytics): Stacktrace:

Caused by java.lang.NullPointerException: Attempt to invoke interface method 'void com.getcapacitor.BridgeWebChromeClient$ActivityResultListener.onActivityResult(androidx.activity.result.ActivityResult)' on a null object reference at com.getcapacitor.BridgeWebChromeClient.lambda$new$1$BridgeWebChromeClient(BridgeWebChromeClient.java:72) at com.getcapacitor.-$$Lambda$BridgeWebChromeClient$nK4HrLIPc8JbAwJyF2CGTpDio8A.onActivityResult(:4) at androidx.activity.result.ActivityResultRegistry.doDispatch(ActivityResultRegistry.java:362) at androidx.activity.result.ActivityResultRegistry.dispatchResult(ActivityResultRegistry.java:322) at androidx.activity.ComponentActivity.onActivityResult(ComponentActivity.java:634) at androidx.fragment.app.FragmentActivity.onActivityResult(FragmentActivity.java:164) at com.getcapacitor.BridgeActivity.onActivityResult(BridgeActivity.java:216) at android.app.Activity.dispatchActivityResult(Activity.java:8413) at android.app.ActivityThread.deliverResults(ActivityThread.java:5464) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4776) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4832) at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:190) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:105) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2386) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:213) at android.app.ActivityThread.main(ActivityThread.java:8178) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)

Any ideas why it can happen?

carlpoole commented 2 years ago

I think activityListener may be null in BridgeWebChromeClient. I'm trying to see what circumstances may be leading to that, can you tell us more about what your app is doing or share the source if possible? Thank you

DontGiveAFck commented 2 years ago

Hi @carlpoole , thanks for your answer! This issue happens on last app version, some of the changes that were included in this version:

  1. Moved from Capacitor v2 to v3
  2. Updated all relevant plugin, libs
  3. Added 'network connection checking' on app launch
  4. OneSignal plugin added (https://github.com/OneSignal/OneSignal-Cordova-SDK)
  5. Orientaion change Subscription.

Since issue happens right after app launch I'm adding code of app.component.ts file:


  import { Component, NgZone, OnInit } from '@angular/core';
  import { Store } from '@ngrx/store';
  import { Platform, Config, AlertController, LoadingController } from '@ionic/angular';
  import { Capacitor, Plugins } from "@capacitor/core";
  import { MobileAccessibility } from '@ionic-native/mobile-accessibility/ngx';
  import { AngularFireAuth } from "@angular/fire/auth";
  import OneSignal from 'onesignal-cordova-plugin';
  import '@joinflux/firebase-remote-config';

  import { FirebaseCrashlytics } from '@capacitor-community/firebase-crashlytics';
  import { FirebaseAnalytics } from "@capacitor-community/firebase-analytics";
  import { AdMob } from '@capacitor-community/admob';
  import { Keyboard } from '@capacitor/keyboard';
  import { GoogleAuth } from '@reslear/capacitor-google-auth'
  import { Network } from "@capacitor/network";
  import { SplashScreen } from "@capacitor/splash-screen";
  import { StatusBar, Style } from '@capacitor/status-bar';

  import { AppApi } from './services/core/api/app.api';
  import { DeviceApi } from './services/core/api/device.api';
  import { IAppState } from './services/core/store/app.reducers';
  import { PurchaseService } from './services/core/services/purchase.service';
  import { APP_CONFIG } from "./services/app-config";
  import { environment } from "../environments/environment";
  import { AuthService } from "./services/core/services/auth.service";
  import { UpdateIsUserLoggedIn } from "./services/core/store/core";
  import { GlobalFuncs } from "./services/global-funcs";
  import { selectUserData, selectIsUserLoggedIn, SetIsTabletLandscape } from "./services/core/store/core";
  import { isJsonString } from "./helpers/helpers";
  import { DeepLinkService } from "./services/core/services/deepLink.service";
  import * as CoreStore from './services/core/store/core';
  import { DeepLink } from "../constants/types";

  const { FirebaseRemoteConfig } = Plugins;

  @Component({
    selector: 'sam-root',
    templateUrl: 'app.component.html',
    styleUrls: ['app.component.scss']
  })
  export class AppComponent implements OnInit {
    private isUserDataUpdatedAfterAppLaunch: boolean = false;
    private firebaseConfigFetched: boolean = false;
    private isUserPremium: boolean = false;

    private refreshReConfigTimer() {
      setTimeout(x => {
        console.log("RE-CONFIGing");
        this.appApi.fetchFireBaseConfig();
        this.refreshReConfigTimer();
      }, APP_CONFIG.TIMER_RECONFIG_MSEC);
    }

    constructor(
      private platform: Platform,
      private config: Config,
      private appApi: AppApi,
      private deviceApi: DeviceApi,
      private mobileAccessibility: MobileAccessibility,
      private store: Store<IAppState>,
      private purchaseService: PurchaseService,
      private authService: AuthService,
      private globalFuncs: GlobalFuncs,
      private fireAuth: AngularFireAuth,
      private deepLinkService: DeepLinkService,
      private ngZone: NgZone,
      private alertCtrl: AlertController,
      private loadingCtrl: LoadingController,
    ) {}

    async ngOnInit(): Promise<void> {
      try {
        await this.checkForNetworkConnectionOnAppStart();

        // initialize BI for vwc
        (window as any).vwcFirebaseLogger = this.globalFuncs.sendAnalEvent;

        this.appApi.fetchVersion();
        this.appApi.fetchCategoryTree();

        this.refreshReConfigTimer();
        this.platform.ready().then(() => {
          const isAndroid = Capacitor.getPlatform() === 'android';

          // initialize google plus for authentication via Google
          GoogleAuth.initialize();

          // initialize One Signal
          this.initOneSignalSDK();

          // Initialize AdMob:
          if (Capacitor.isNativePlatform()) {
            AdMob.initialize({
              requestTrackingAuthorization: false,
            }).then(() => {
              // prepare interstitial ad
              const options = {
                adId: isAndroid
                  ? environment.ANDROID_UNIT_ID_FOR_INTERSTITIAL
                  : environment.IOS_AD_UNIT_ID_FOR_INTERSTITIAL,
                // show npa only for ios
                npa: !isAndroid,
                // isTesting: !environment.production,
              };
              AdMob.prepareInterstitial(options);
              // ---
            });
          }

          // portrait orientation lock
          if (this.platform.is('mobile')) {
            window.screen.orientation.lock('portrait');
          }

          this.updateAndSubscribeToTabletLandscape();

          // fix keyboard issue on iPad
          if (!isAndroid) {
            Keyboard.addListener('keyboardDidShow', () => {
              const activeElement = document.activeElement;
              setTimeout(() => {
                const ionContent = document.querySelector('page-editor ion-content');
                if (ionContent && ionContent.shadowRoot) {
                  const scrollElement = ionContent.shadowRoot.querySelector('.inner-scroll');
                  scrollElement.scroll({
                    top: scrollElement.scrollTop + 2,
                    behavior: 'smooth'
                  });
                }
              }, 200);
            });
          }

          // enable crashlytics
          FirebaseCrashlytics.setEnabled({
            enabled: true
          });

          this.store.select(selectIsUserLoggedIn).subscribe((isLoggedIn) => {
            // skip initial value and don't configure purchasing before emitting value from onAuthStateChanged
            if (isLoggedIn === null) return;

            if (isLoggedIn) {
              // get favorites
              this.store.dispatch(new CoreStore.GetFavorites());
            } else {
              this.store.dispatch(new CoreStore.UpdateFavorites([]));
              this.appApi.updateUserData({
                isPremiumOnWebsite: false
              });
            }
            if (this.firebaseConfigFetched) {
              // configure purchases (receiving data from app store/google play)
              // if user has subscription in app store/google play premium feature will be granted
              // also it will call validation service with firebaseID if user logged in.
              this.purchaseService.configurePurchasing();
            } else {
              FirebaseRemoteConfig.initialize({
                // @ts-ignore
                minimumFetchIntervalInSeconds: 5,
              });
              this.appApi.fetchFireBaseConfig();

              this.firebaseConfigFetched = true;
            }
          });

          this.fireAuth.authState.subscribe(async (user) => {
            if (user) {
              // update user data on app init
              if (!this.isUserDataUpdatedAfterAppLaunch) {
                this.store.dispatch(new UpdateIsUserLoggedIn(true));
                const res = await this.authService.loginUserOnServer();
                if (res.isPremium) {
                  this.globalFuncs.runIOSCallbackInAngularScope(() => {
                    this.appApi.updateUserData({
                      isPremiumOnWebsite: true
                    })
                  });
                }
              }
              try {
                if (Capacitor.isNativePlatform()) {
                  FirebaseAnalytics.setUserId({
                    userId: user.uid
                  });
                }
              }
              catch (ex){
                console.error("Failed at FirebaseAnalytics.setUserId", ex);
              }
            } else {
              this.store.dispatch(new UpdateIsUserLoggedIn(false));
            }
            this.isUserDataUpdatedAfterAppLaunch = true;
          });

          this.store.select(selectUserData).subscribe((userData) => {
            this.isUserPremium = userData.isPremiumOnWebsite || userData.isPremiumInMobileStore;
          });

          // AppsFlyer configuration
          this.initAppsflyerSDK();
        });

        try {
          this.mobileAccessibility.usePreferredTextZoom(false);
        }
        catch (ee) {
          console.error("usePreferredTextZoom failed", ee);
        }

        if (Capacitor.isNativePlatform()) {
          StatusBar.setStyle({
            style: Style.Light
          });
          // not supported on iOS:
          if (Capacitor.getPlatform() !== 'ios') {
            StatusBar.setBackgroundColor({ color: '#f8f8f8' });
          }
        }
      } catch (error) {
        this.globalFuncs.sendAnalError('error.sam.init', error);
      }
    }

    async initAppsflyerSDK() {
      // checking network connection before first
      // required for SDK init
      await this.globalFuncs.awaitForConnectionToNetwork();

      const isAndroid = this.platform.is('android');
      // @ts-ignore
      const appsFlyer: any = window.plugins.appsFlyer;

      const onSuccess = (res) => {
        try {
          // for some reason can be object or serialized object
          const conversionData = JSON.parse(res);
          const data = isJsonString(conversionData.data)
            ? JSON.parse(conversionData.data)
            : conversionData.data;

          // for some reason behaves differently on ios/android
          const shouldHandleDeepLinks = data.is_first_launch;
          if (shouldHandleDeepLinks) {
            this.deepLinkService.openDeepLink(data.deep_link_value, data.deep_link_sub1 ?  { categoryId: data.deep_link_sub1 } : {}, 'deeplink');
          }
        } catch (error) {
          console.error('Appsflyer initSdk onSuccess error: ', error);
        }
      };

      appsFlyer.registerDeepLink((res) => {
        try {
          const parsedRes = isJsonString(res) ? JSON.parse(res) : res;
          const data = isJsonString(parsedRes.data)
            ? JSON.parse(parsedRes.data)
            : parsedRes.data;
          this.deepLinkService.openDeepLink(data.deep_link_value, data.deep_link_sub1 ?  { categoryId: data.deep_link_sub1 } : {}, 'deeplink');
        } catch (error) {
          console.error('registerDeepLink error: ', error);
        }
      });

      try {
        const options = {
          devKey: 'KEY',
          isDebug: !environment.production,
          appId: 'KEY',
          onDeepLinkListener: true,
          onInstallConversionDataListener: true
        };

        appsFlyer.initSdk(options, isAndroid ? onSuccess : () => {});
      } catch (error) {
        console.error('appsFlyer.initSdk error', error);
      }
    }

    private initOneSignalSDK(): void {
      // Uncomment to set OneSignal device logging to VERBOSE
      // OneSignal.setLogLevel(6, 0);

      // NOTE: Update the setAppId value below with your OneSignal AppId.
      OneSignal.setAppId(Capacitor.getPlatform() === 'ios' ? 'KEY' : 'KEY');
      OneSignal.setNotificationOpenedHandler((jsonData) => {
        const deepLink: DeepLink = (jsonData.notification.additionalData as any).deepLink;
        const categoryId: any = (jsonData.notification.additionalData as any).categoryId;
        this.deepLinkService.openDeepLink(deepLink, { categoryId }, 'notification_link');
      });
    }

    private async checkForNetworkConnectionOnAppStart() {
      const { connected } = await Network.getStatus();
      if (!connected) {
        const loader = await this.loadingCtrl.create({
          message: 'Please establish an internet connection.',
          backdropDismiss: false
        });
        await loader.present();
        await SplashScreen.hide();
        await this.awaitForConnectionToNetwork();
        await SplashScreen.show();
        await loader.dismiss();
      }
    }

    private updateAndSubscribeToTabletLandscape() {
      const isTabletLandscape = this.platform.is('tablet') && this.platform.isLandscape();
      this.store.dispatch(new SetIsTabletLandscape(isTabletLandscape));
      // async actions that runs out of angular scope - ngZone.run() required.
      window.screen.orientation.onchange = (orientation) => {
        this.globalFuncs.runIOSCallbackInAngularScope(() => {
          const isTabletLandscape = this.platform.is('tablet') && this.platform.isLandscape();
          this.store.dispatch(new SetIsTabletLandscape(isTabletLandscape));
        });
      };
    }
  }

    // promise will be resolved only when connection to network will be established
    awaitForConnectionToNetwork(): Promise<void> {
      return new Promise(async (resolve) => {
        const { connected } = await Network.getStatus();
        if (connected) {
          resolve()
        } else {
          const listener = Network.addListener('networkStatusChange', async ({ connected }) => {
            if (connected) {
              await listener.remove();
              resolve();
            }
          });
        }
      })
    }

Also adding packages list:

"dependencies": {
    "@angular/common": "~12.1.5",
    "@angular/core": "~12.1.5",
    "@angular/fire": "^6.1.5",
    "@angular/forms": "~12.1.5",
    "@angular/platform-browser": "~12.1.5",
    "@angular/platform-browser-dynamic": "~12.1.5",
    "@angular/router": "~12.1.5",
    "@capacitor-community/admob": "^3.2.0",
    "@capacitor-community/apple-sign-in": "^1.0.1",
    "@capacitor-community/facebook-login": "^3.1.1",
    "@capacitor-community/firebase-analytics": "^1.0.0",
    "@capacitor-community/firebase-crashlytics": "^1.1.0",
    "@capacitor-community/media": "git+https://github.com/dragermrb/media.git",
    "@capacitor/android": "^3.2.4",
    "@capacitor/app": "^1.0.3",
    "@capacitor/app-launcher": "^1.0.4",
    "@capacitor/camera": "^1.1.0",
    "@capacitor/core": "^3.2.4",
    "@capacitor/filesystem": "^1.0.3",
    "@capacitor/haptics": "^1.1.0",
    "@capacitor/ios": "^3.2.4",
    "@capacitor/keyboard": "^1.1.0",
    "@capacitor/network": "^1.0.3",
    "@capacitor/share": "^1.0.4",
    "@capacitor/splash-screen": "^1.1.3",
    "@capacitor/status-bar": "^1.0.3",
    "@firebase/remote-config": "^0.1.41",
    "@ionic-native/app-rate": "5.32.1",
    "@ionic-native/appsflyer": "^5.34.0",
    "@ionic-native/core": "^5.34.0",
    "@ionic-native/file": "^5.34.0",
    "@ionic-native/in-app-purchase-2": "^5.34.0",
    "@ionic-native/mobile-accessibility": "^5.34.0",
    "@ionic-native/native-storage": "^5.34.0",
    "@ionic-native/open-native-settings": "^5.34.0",
    "@ionic-native/printer": "^5.36.0",
    "@ionic/angular": "^5.8.4",
    "@ionic/storage": "^2.3.1",
    "@joinflux/firebase-remote-config": "^0.5.0",
    "@ngrx/effects": "^12.1.0",
    "@ngrx/store": "^12.1.0",
    "@ngrx/store-devtools": "^12.1.0",
    "@reslear/capacitor-google-auth": "^3.1.0",
    "capacitor-email-composer": "^1.0.0",
    "cordova-open-native-settings": "^1.5.5",
    "cordova-plugin-apprate": "1.5.0",
    "cordova-plugin-appsflyer-sdk": "6.2.60",
    "cordova-plugin-device": "git+https://github.com/apache/cordova-plugin-device.git",
    "cordova-plugin-dialogs": "^2.0.2",
    "cordova-plugin-file": "^6.0.2",
    "cordova-plugin-globalization": "^1.11.0",
    "cordova-plugin-inappbrowser": "^4.1.0",
    "cordova-plugin-nativestorage": "^2.3.2",
    "cordova-plugin-network-information": "git+https://github.com/apache/cordova-plugin-network-information.git",
    "cordova-plugin-printer": "^0.8.0",
    "cordova-plugin-purchase": "^10.6.0",
    "cordova-plugin-screen-orientation": "^3.0.2",
    "cordova-sqlite-storage": "^6.0.0",
    "es6-promise-plugin": "^4.2.2",
    "firebase": "^8.7.0",
    "globalthis": "^1.0.2",
    "jetifier": "^1.6.8",
    "onesignal-cordova-plugin": "^3.0.0",
    "phonegap-plugin-mobile-accessibility": "^1.0.5",
    "rxjs": "^6.6.7",
    "rxjs-compat": "^6.6.7",
    "tslib": "^2.3.0",
    "vue": "^2.6.14",
    "zone.js": "~0.11.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.1000.0",
    "@angular/cli": "~12.1.4",
    "@angular/compiler": "~12.1.4",
    "@angular/compiler-cli": "~12.1.4",
    "@angular/language-service": "~12.1.4",
    "@capacitor/cli": "^3.2.4",
    "@firebase/util": "^0.4.1",
    "@ionic/angular-toolkit": "^2.3.0",
    "@types/jasmine": "~3.5.0",
    "@types/jasminewd2": "^2.0.9",
    "@types/node": "^12.20.15",
    "codelyzer": "^6.0.2",
    "cross-env": "^7.0.3",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "~3.3.0",
    "karma-jasmine-html-reporter": "^1.6.0",
    "prop-types": "^15.7.2",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "^4.2.3"
  }
jcesarmobile commented 2 years ago

Does you app has some input with file type? If so, can you share it? I think that's the only thing in the WebChromeClient that could cause the callback to be called, but not sure what could lead to activityListener being null.

Does it happen in specific android versions or devices? I think crashlytics should provide that kind of information.

ionitron-bot[bot] commented 1 year ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Capacitor, please create a new issue and ensure the template is fully filled out.