Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
DontGiveAFck opened this issue Nov 30, 2021 · 4 comments · Fixed by #5328
Closed

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

DontGiveAFck opened this issue Nov 30, 2021 · 4 comments · Fixed by #5328

Comments

@DontGiveAFck
Copy link

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
Copy link
Member

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
Copy link
Author

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
Copy link
Member

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
Copy link

ionitron-bot bot commented Nov 10, 2022

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.

@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Nov 10, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants