1

Background: I am trying to develop a flutter app that connects with 3 different Firebase projects. I followed the instructions on FlutterFire, specifically on FlutterFire Overview and Core | FlutterFire and initialized 3 firebase projects apps inside App.

Note: I have use to 3 different Firebase projects for legality reasons and I cannot use flavors. Also, the app uses shared preferences to store data locally.

Problem: When I start the emulator and run the app everything seems to work fine. The app starts, connects to all three firebase projects and does everything I want it to. However, now if I try to re-run the app (i.e. stop the app and then press the green arrow to restart it), the app crashes with the error message:

D/AndroidRuntime(16200): Shutting down VM
E/AndroidRuntime(16200): FATAL EXCEPTION: main
E/AndroidRuntime(16200): Process: com.example.flutter_app, PID: 16200
E/AndroidRuntime(16200): java.lang.RuntimeException: Internal error in Cloud Firestore (22.0.1).
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue.lambda$panic$3(AsyncQueue.java:534)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$$Lambda$3.run(Unknown Source:2)
E/AndroidRuntime(16200): at android.os.Handler.handleCallback(Handler.java:883)
E/AndroidRuntime(16200): at android.os.Handler.dispatchMessage(Handler.java:100)
E/AndroidRuntime(16200): at android.os.Looper.loop(Looper.java:214)
E/AndroidRuntime(16200): at android.app.ActivityThread.main(ActivityThread.java:7356)
E/AndroidRuntime(16200): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(16200): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
E/AndroidRuntime(16200): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
E/AndroidRuntime(16200): Caused by: java.lang.RuntimeException: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor.lambda$executeAndReportResult$1(AsyncQueue.java:325)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$$Lambda$2.run(Unknown Source:4)
E/AndroidRuntime(16200): at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
E/AndroidRuntime(16200): at java.util.concurrent.FutureTask.run(FutureTask.java:266)
E/AndroidRuntime(16200): at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
E/AndroidRuntime(16200): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/AndroidRuntime(16200): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:229)
E/AndroidRuntime(16200): at java.lang.Thread.run(Thread.java:919)
E/AndroidRuntime(16200): Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:648)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteSession.beginTransactionUnchecked(SQLiteSession.java:325)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteSession.beginTransaction(SQLiteSession.java:300)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:568)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteDatabase.beginTransactionWithListener(SQLiteDatabase.java:531)
E/AndroidRuntime(16200): at com.google.firebase.firestore.local.SQLitePersistence.runTransaction(SQLitePersistence.java:190)
E/AndroidRuntime(16200): at com.google.firebase.firestore.local.LocalStore.startMutationQueue(LocalStore.java:159)
E/AndroidRuntime(16200): at com.google.firebase.firestore.local.LocalStore.start(LocalStore.java:155)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.ComponentProvider.initialize(ComponentProvider.java:138)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.FirestoreClient.initialize(FirestoreClient.java:249)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.FirestoreClient.lambda$new$0(FirestoreClient.java:96)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.FirestoreClient$$Lambda$1.run(Unknown Source:8)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue.lambda$enqueue$2(AsyncQueue.java:436)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$$Lambda$2.call(Unknown Source:2)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor.lambda$executeAndReportResult$1(AsyncQueue.java:322)
E/AndroidRuntime(16200): ... 8 more
I/Process (16200): Sending signal. PID: 16200 SIG: 9
Lost connection to device.

I only get this error when I have multiple firebase projects being initialized and am re-starting the app (without restarting the emulator).

Do you know what is causing this error and how to fix it?

Code:

Future<void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    runApp(App());
}

class App extends StatefulWidget {
    @override
    _AppState createState() => _AppState();
}

class _AppState extends State<App> {
    @override
    void initState() {
        print("Constructing app");
        super.initState();
    }

    final Future<FirebaseApp> firebaseApp = Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key-1",
            appId: "app-id-1",
            projectId: "project-id-1",
            messagingSenderId: "sender-id-1"));
    final Future<FirebaseApp> firebaseAppDiscussions = Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key-2",
            appId: "app-id-2",
            projectId: "project-id-2",
            messagingSenderId: "sender-id-2"),
        name: "app2");
    final Future<FirebaseApp> firebaseAppComments = Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key-3",
            appId: "app-id-3",
            projectId: "project-id-3",
            messagingSenderId: "sender-id-3"),
        name: "app3");
    // My app also uses Shared Preferences, but I am not sure if it is playing a part in causing this error.
    final Future<SharedPreferences> sharedPrefStore = SharedPreferences.getInstance();
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'My App',
            home: FutureBuilder(
                future: Future.wait([
                    firebaseApp,
                    sharedPrefStore,
                    firebaseAppDiscussions,
                    firebaseAppComments
                ]),
                builder: (context, AsyncSnapshot<List<dynamic>> snapshot) { 
                    auth = FirebaseAuth.instance;
                    authDiscussion = FirebaseAuth.instanceFor(app: snapshot.data![2]);
                    authComment = FirebaseAuth.instanceFor(app: snapshot.data![3]);
                    firestoreInstance = FirebaseFirestore.instance;
                    firestoreInstanceDiscussion = FirebaseFirestore.instanceFor(app: snapshot.data![2]);
                    firestoreInstanceComment = FirebaseFirestore.instanceFor(app: snapshot.data![3]);
                    prefs = snapshot.data![1]; // sharedPrefStore
                    // If I comment out the if statement below, the app will work. 
                    // However, with the if statement, a crash is certain.
                    if (auth.currentUser?.uid == null) {
                        print("No user logged in");
                    }
                    return Text("App has loaded");
                }
        ); 
    }
}

Steps to produce error:

  1. Start the emulator and run the app by pressing the green arrow button
  2. After the app is up and running, press the red button to stop execution.
  3. Restart the app by pressing the green arrow button

Additional information:

  1. Each Firebase project is using Cloud Firestore and Firebase Auth, which I have initialized inside future builder in the following manner:
auth = FirebaseAuth.instance;
authDiscussion = FirebaseAuth.instanceFor(app: snapshot.data![2]);
authComment = FirebaseAuth.instanceFor(app: snapshot.data![3]);
firestoreInstance = FirebaseFirestore.instance;
firestoreInstanceDiscussion = FirebaseFirestore.instanceFor(app: snapshot.data![2]);
firestoreInstanceComment = FirebaseFirestore.instanceFor(app: snapshot.data![3]);
prefs = snapshot.data![1]; // sharedPrefStore  
  1. When I start the app the first time, I have absolutely no errors and all three projects can access their respective Cloud Firestores and Firebase Auth.

  2. Also, I am testing my app on the Pixel 2 (API 29) emulator provided by Android Studio.

  3. I have been searching online for solutions, but most people encounter this error when using Firebase with persistence enabled (ex: Firebase Database crash SQLiteDatabaseLockedException) -- which I am not using.

  4. I have tried disabling shared preferences, but to no avail. The app still crashes when performing a re-start.

My hypothesis: I have a hunch that when I re-run the app, it re-constructs App which in turn reinitializes my three firebase projects leading to the error. I tried to validate this theory by placing a print inside the initializeState() method in App to track when App is re-constructed. And sure enough, whenver initializeState() is called more than once, my app crashes.

But I have no idea how I can "tell" the app to not reinitialize the projects.

Edit #1: I understand the error is concerned with SQLite Database being locked. However, (I don't think) I am using any such database. I don't use sqflite; instead, my app uses shared preferences to persist data locally (as it only persists a small amount of data), and that's it.

Edit #2: I have updated my code snippet above to include the entire code that I was testing. As you can see, I don't explicitly initialize SQL database anywhere. So, it is likely that Firebase.initializeApp uses/locks SQL database internally. And since I have multiple calls to Firebase.initializeApp -- all which will access the same SQL database -- the calls block on each other resulting in the error "SQL database locked."

If this is indeed the case, the error should go away if I wait for each Firebase.initializeApp call to complete before calling it again for the next Firebase project.

This is my current theory, and I am working to modify my code initialize my firebase projects one after the other.

Solution: My theory (mentioned in "Edit #2") was valid. I modified my code as stated above and all the errors went away.

Here's my complete, working code with comments.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

/// Here is my updated code that initializes Firebase projects one after another to ensure they don't clash on the SQL database.
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    print("Constructing my app");
    super.initState();
  }

  final Future<SharedPreferences> sharedPrefStore =
      SharedPreferences.getInstance(); // This is used by my app later on and is not really relevant to the error.

  // Functions to initialize all 3 apps. I use these functions only help make the code look cleaner.
  Future<FirebaseApp> initFirebase3() {
    return Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key3",
            appId: "app-id3",
            projectId: "project-id3",
            messagingSenderId: "sender-id3"),
        name: "app3");
  }

  Future<FirebaseApp> initFirebase2() {
    return Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key2",
            appId: "app-id2",
            projectId: "project-id2",
            messagingSenderId: "sender-id2"),
        name: "app2");
  }

  Future<FirebaseApp> initFirebaseDefault() {
    return Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key1",
            appId: "app-id1",
            projectId: "project-id1",
            messagingSenderId: "sender-id1"));
  }

  @override
  Widget build(BuildContext context) {
    print("Building App.");
    return MaterialApp(
        title: 'My App',
        home: FutureBuilder(
            // sharedPrefStore and Firebase.initializeApp can run in parallel since they are independent of each other.
            future: Future.wait([initFirebaseDefault(), sharedPrefStore]),
            builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
              if (snapshot.hasError) {
                print("You have an error! ${snapshot.error.toString()}");
                return Text('Oops! Something went wrong. Please try again');
              } else if (snapshot.hasData) {
                // Initialize firestore and auth (global variables) using the completed future
                auth = FirebaseAuth.instance;

                firestoreInstance = FirebaseFirestore.instance;

                prefs = snapshot.data![1]; // sharedPrefStore

                // Now on to app2. Before we initialize app2, we must first check if it already exists. 
                // If it already exists and we try to initialize it again, we will get the error "app2 already exists"
                late FirebaseApp? app2;
                try {
                  app2 = Firebase.app("app2");
                } catch (e) {
                  print("app2 not initialized");
                  app2 = null;
                }
                if (app2 != null) {
                  auth2 = FirebaseAuth.instanceFor(app: app2);

                  firestoreInstance2 = FirebaseFirestore.instanceFor(app: app2);

                  // Same as above. Before we initialize app3, we must first check if it already exists. 
                  // If it already exists and we try to initialize it again, we will get the error "app3 already exists"
                  late FirebaseApp? app3;
                  try {
                    app3 = Firebase.app("app3");
                  } catch (e) {
                    print("app3 not initialized");
                    app3 = null;
                  }
                  if (app3 == null) {
                    return FutureBuilder(
                        future: initFirebase3(),
                        builder:
                            (context, AsyncSnapshot<FirebaseApp> snapshot) {
                          if (snapshot.hasError) {
                            print(
                                "You have an error! ${snapshot.error.toString()}");
                            return Text(
                                'Oops! Something went wrong. Please try again');
                          } else if (snapshot.hasData) {
                            auth3 = FirebaseAuth.instanceFor(app: snapshot.data!);

                            firestoreInstance3 = FirebaseFirestore.instanceFor(
                                app: snapshot.data!);
                            
                            return Scaffold(
                              body: Text("app has loaded"),
                            );
                          } else {
                            return Center(
                              child: CircularProgressIndicator(),
                            );
                          }
                        });
                  } else {
                    auth3 = FirebaseAuth.instanceFor(app: app3);

                    firestoreInstance3 = FirebaseFirestore.instanceFor(app: app3);
                    
                    return Scaffold(
                      body: Text("app has loaded"),
                    );
                  }
                }
                // app2 was null so we came here; now we must initialize app2
                return FutureBuilder(
                    future: initFirebase2(),
                    builder: (context, AsyncSnapshot<FirebaseApp> snapshot) {
                      if (snapshot.hasError) {
                        print(
                            "You have an error! ${snapshot.error.toString()}");
                        return Text(
                            'Oops! Something went wrong. Please try again');
                      } else if (snapshot.hasData) {
                        auth2 = FirebaseAuth.instanceFor(app: snapshot.data!);

                        firestoreInstance2 =
                            FirebaseFirestore.instanceFor(app: snapshot.data!);

                        // I assume that if app2 was not initialized neither was app3, hence I call initFirebase3().
                        return FutureBuilder(
                            future: initFirebase3(),
                            builder:
                                (context, AsyncSnapshot<FirebaseApp> snapshot) {
                              if (snapshot.hasError) {
                                print(
                                    "You have an error! ${snapshot.error.toString()}");
                                return Text(
                                    'Oops! Something went wrong. Please try again');
                              } else if (snapshot.hasData) {
                                auth3 = FirebaseAuth.instanceFor(
                                    app: snapshot.data!);

                                firestoreInstance3 =
                                    FirebaseFirestore.instanceFor(
                                        app: snapshot.data!);
                                
                                return Scaffold(
                                    body: Text("app has loaded"),
                                );
                              } else {
                                return Center(
                                    child: CircularProgressIndicator(),
                                );
                              }
                            });
                      } else {
                        return Center(
                          child: CircularProgressIndicator(),
                        );
                      }
                    });
              } else {
                return Center(
                  child: CircularProgressIndicator(),
                );
              }
            }));
  }
}

Please let me know if additional information is needed. Any help would be greatly appreciated.

Thank you

1 Answer 1

0

It's not a firebase issue, it's an SQFlite issue. The error says:

Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
E/AndroidRuntime(16200)

It has something to do with what happens in your code after you initialize the apps. If you are fetching magnitudes of data, causing heavy writes to your SQFlite database, it causes it to lock.

Sign up to request clarification or add additional context in comments.

3 Comments

I am actually not fetching magnitudes of data. I am only reading data from one firebase project and that one too only has one tiny document (only 5 fields) inside Cloud Firestore. I am not doing any writes. Also, if it had something to do with fetching magnitudes of data, won't the app crash every single time? Why does it only crash when restarting?
The error is clearly pointing to sql, share the code related to initializing that. Maybe it's being caught in a loop that rebuilds it. To validate this, comment out the code that triggers writes to your SQLite and see if it crashes.
I updated my code snippet in the post to show my entire test code. As you can see, I am not initializing/calling SQL database anywhere. Hence, I believe linking multiple firebase projects is the problem (please see "Edit #2" section in post).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.