Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase Remote Config : NullPointerException on Map.keySet()

I am getting the following crash for a lot of users :

Fatal Exception: java.lang.NullPointerException: Attempt to invoke interface method 'java.util.Set java.util.Map.keySet()' on a null object reference
   at com.google.android.gms.internal.zzbtn.zza(Unknown Source)
   at com.google.android.gms.internal.zzbtn.run(Unknown Source)
   at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
   at java.lang.Thread.run(Thread.java:818)

I searched through external libraries and found that the source of crash is firebase-config library in the following class zzbtn(see method - zza()).

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.google.android.gms.internal;

import android.content.Context;
import android.util.Log;
import com.google.android.gms.internal.zzbtl;
import com.google.android.gms.internal.zzbto;
import com.google.android.gms.internal.zzbtr;
import com.google.android.gms.internal.zzbxt;
import com.google.android.gms.internal.zzbts.zza;
import com.google.android.gms.internal.zzbts.zzb;
import com.google.android.gms.internal.zzbts.zzc;
import com.google.android.gms.internal.zzbts.zzd;
import com.google.android.gms.internal.zzbts.zze;
import com.google.android.gms.internal.zzbts.zzf;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class zzbtn implements Runnable {
public final Context mContext;
public final zzbto zzclY;
public final zzbto zzclZ;
public final zzbto zzcma;
public final zzbtr zzclQ;

    public zzbtn(Context var1, zzbto var2, zzbto var3, zzbto var4, zzbtr var5) {
        this.mContext = var1;
        this.zzclY = var2;
        this.zzclZ = var3;
        this.zzcma = var4;
        this.zzclQ = var5;
    }

    private zza zza(zzbto var1) {
        zza var2 = new zza();
        if(var1.zzace() != null) {
            Map var3 = var1.zzace();
            ArrayList var4 = new ArrayList();
            Iterator var5 = var3.keySet().iterator();

            while(var5.hasNext()) {
                String var6 = (String)var5.next();
                ArrayList var7 = new ArrayList();
                Map var8 = (Map)var3.get(var6);
                /* Crash is here when reading keySet() on null map */
                Iterator var9 = var8.keySet().iterator();

                while(var9.hasNext()) {
                    String var10 = (String)var9.next();
                    zzb var11 = new zzb();
                    var11.zzaB = var10;
                    var11.zzcml = (byte[])var8.get(var10);
                    var7.add(var11);
                }

                zzd var16 = new zzd();
                var16.zzaGP = var6;
                zzb[] var12 = new zzb[var7.size()];
                var16.zzcmq = (zzb[])var7.toArray(var12);
                var4.add(var16);
            }

            zzd[] var15 = new zzd[var4.size()];
            var2.zzcmi = (zzd[])var4.toArray(var15);
        }

        if(var1.zzzD() != null) {
            List var13 = var1.zzzD();
            byte[][] var14 = new byte[var13.size()][];
            var2.zzcmj = (byte[][])var13.toArray(var14);
        }

        var2.timestamp = var1.getTimestamp();
        return var2;
    }

    public void run() {
        zze var1 = new zze();
        if(this.zzclY != null) {
            var1.zzcmr = this.zza(this.zzclY);
        }

        if(this.zzclZ != null) {
            var1.zzcms = this.zza(this.zzclZ);
        }

        if(this.zzcma != null) {
            var1.zzcmt = this.zza(this.zzcma);
        }

        if(this.zzclQ != null) {
            zzc var2 = new zzc();
            var2.zzcmm = this.zzclQ.getLastFetchStatus();
            var2.zzcmn = this.zzclQ.isDeveloperModeEnabled();
            var2.zzcmo = this.zzclQ.zzacj();
            var1.zzcmu = var2;
        }

        if(this.zzclQ != null && this.zzclQ.zzach() != null) {
            ArrayList var8 = new ArrayList();
            Map var3 = this.zzclQ.zzach();
            Iterator var4 = var3.keySet().iterator();

            while(var4.hasNext()) {
                String var5 = (String)var4.next();
                if(var3.get(var5) != null) {
                    zzf var6 = new zzf();
                    var6.zzaGP = var5;
                    var6.zzcmx = ((zzbtl)var3.get(var5)).zzacd();
                    var6.resourceId = ((zzbtl)var3.get(var5)).zzacc();
                    var8.add(var6);
                }
            }

            zzf[] var11 = new zzf[var8.size()];
            var1.zzcmv = (zzf[])var8.toArray(var11);
        }

        byte[] var9 = zzbxt.zzf(var1);

        try {
            FileOutputStream var10 = this.mContext.openFileOutput("persisted_config", 0);
            var10.write(var9);
            var10.close();
        } catch (IOException var7) {
            Log.e("AsyncPersisterTask", "Could not persist config.", var7);
        }

    }
}

This class is used in FirebaseRemoteConfig class (see method zzt()) as given below :

https://gist.github.com/anonymous/e6f23c1dc37bf905a9224d8b72ab6cd9

And I am using FirebaseRemoteConfig in my application class as below :

public class MyApp extends MultiDexApplication {

    public static boolean sound = true;
    private static Context context;

    private Typeface regularTypeFace;
    private Typeface boldTypeFace;
    private final String LOG_TAG = "MyApp";
    private FirebaseRemoteConfig remoteConfig = null;

    @Override
    public void onCreate() {
        super.onCreate();

        Fabric.with(this, new Crashlytics());

        context = getApplicationContext();

        /* Try catch to handle any runtime exception thrown by firebase API */
        try {
            /* Initialize firebase app : Done to avoid crashes due to IllegalStateException - Default FirebaseApp is not initialized */
            FirebaseApp.initializeApp(this);
            /* Start to fetch Remote config parameters */
            startConfigFetch();
        } catch (Exception e){
            e.printStackTrace();
        }

    }

    /**
     * Description : Fetches remote config params from firebase & uses the fetched values
     */
    public void startConfigFetch() {

        /* try to get the default instance of Firebase Remote config */
        /* try-catch : To Resolve Crash #1399 */
        try {
            remoteConfig = FirebaseRemoteConfig.getInstance();
        } catch (IllegalStateException e){
            e.printStackTrace();
        }

        /* If we don't get an instance of Firebase remote config, then do nothing */
        if (remoteConfig == null){
            return;
        }

        FirebaseRemoteConfigSettings remoteConfigSettings = new FirebaseRemoteConfigSettings.Builder()
                .setDeveloperModeEnabled(BuildConfig.DEBUG)
                .build();

        remoteConfig.setConfigSettings(remoteConfigSettings);
        remoteConfig.setDefaults(R.xml.remote_config_defaults);

        /* Time for which cache lives, for now its 0 ms */
        long cacheExpiration = 0;

        OnCompleteListener<Void> onCompleteListener = new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {

                if (task.isSuccessful()) {
                    if (getContext() != null) {
                        onFetchConfigSuccess();
                    }
                } else {
                    Log.d(LOG_TAG, "Stories Fetch Fail");
                }
            }
        };

        OnFailureListener onFailListener = new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.d(LOG_TAG, "Stories Fetch Fail in remote config");
            }
        };

        remoteConfig.fetch(cacheExpiration).addOnCompleteListener(onCompleteListener).addOnFailureListener(onFailListener);
    }

    /**
     * Called when fetch config is success
     * */
    private void onFetchConfigSuccess() {

        /* Once the config is successfully fetched it must be activated before newly fetched */
        /* values are returned (or can be used) */
        remoteConfig.activateFetched();

        /* Get dynamic stories string */
        String dynamicStories = remoteConfig.getString(Common.KEY_DYNAMIC_STORIES_FIREBASE_CONFIG);
        /* Get bundled stories (these are app-bundled stories & dynamic stories but part of bundled stories) */
        String bundledStories = remoteConfig.getString(Common.KEY_BUNDLED_STORIES_FIREBASE_CONFIG);

    /* Do something with dynamicStories & bundledStories values */
    }

}

And class zzbto is here :

https://gist.github.com/anonymous/e2f3a67e6fd3be51ba4456fe2e847890

Please help to resolve this crash.

like image 983
Ashutosh Chamoli Avatar asked Feb 21 '17 08:02

Ashutosh Chamoli


2 Answers

As stated in Kirril Karmizin's comment, it's a known bug since June 2017 (Github Firebase issues). I also received multiple crash reports

Fatal Exception: java.lang.NullPointerException
   at com.google.android.gms.internal.zzdyx.zzb(Unknown Source)
   at com.google.android.gms.internal.zzdyx.zza(Unknown Source)
   at com.google.android.gms.internal.zzfan.run(Unknown Source)
   at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
   at java.lang.Thread.run(Thread.java:848)

, with:

  • Firebase: 12.0.1
  • buildTools: 27.0.3

A probable step in the right direction for those that would want to keep Remote Config in their apps can be found in Official RemoteConfic docs:

You should set in-app default parameter values in the Remote Config object, so that your app behaves predictably before it fetches values from the Remote Config service.

For me, I could not risk more crash reports in a production app. I removed Remote Config and I've seen no crashes until now.

like image 66
Ευάγγελος Μπίλης Avatar answered Oct 13 '22 01:10

Ευάγγελος Μπίλης


The bug @Ευάγγελος pointed out was fixed in version 15.0.0. However, that version introduced another bug that was also causing NullPointerException. Fortunately, it was quickly fixed in 15.0.2.

like image 36
David Miguel Avatar answered Oct 13 '22 01:10

David Miguel