简体   繁体   English

在一个 Flutter 应用中集成多个 Firebase 项目会导致应用崩溃

[英]Integrating multiple Firebase projects in one Flutter app causes app to crash

Background: I am trying to develop a flutter app that connects with 3 different Firebase projects.背景:我正在尝试开发一个连接 3 个不同 Firebase 项目的 Flutter 应用程序。 I followed the instructions on FlutterFire, specifically on FlutterFire Overview and Core |我遵循 FlutterFire 上的说明,特别是FlutterFire 概述核心 | FlutterFire and initialized 3 firebase projects apps inside App. FlutterFire并在 App 内初始化了 3 个firebase项目应用程序。

Note: I have use to 3 different Firebase projects for legality reasons and I cannot use flavors.注意:出于合法性原因,我已经使用了 3 个不同的 Firebase 项目,并且我不能使用口味。 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.该应用程序启动,连接到所有三个 firebase 项目并执行我想要的所有操作。 However, now if I try to re-run the app (ie 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).我只有在初始化多个 firebase 项目并重新启动应用程序(不重新启动模拟器)时才会收到此错误。

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:每个 Firebase 项目都使用 Cloud Firestore 和 Firebase Auth,我在未来的构建器中以以下方式对其进行了初始化:
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.当我第一次启动应用程序时,我绝对没有错误,所有三个项目都可以访问各自的 Cloud Firestores 和 Firebase Auth。

  2. Also, I am testing my app on the Pixel 2 (API 29) emulator provided by Android Studio.此外,我正在 Android Studio 提供的 Pixel 2 (API 29) 模拟器上测试我的应用程序。

  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.我一直在网上搜索解决方案,但大多数人在使用启用持久性的 Firebase 时遇到此错误(例如: Firebase 数据库崩溃 SQLiteDatabaseLockedException ) - 我没有使用。

  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.我的假设:我有一种预感,当我重新运行该应用程序时,它会重新构建App ,从而重新初始化导致错误的三个 firebase 项目。 I tried to validate this theory by placing a print inside the initializeState() method in App to track when App is re-constructed.我试图通过在AppinitializeState()方法中放置一个打印来验证这个理论,以跟踪App何时重新构建。 And sure enough, whenver initializeState() is called more than once, my app crashes.果然,只要多次调用initializeState() ,我的应用就会崩溃。

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.编辑 #1:我知道错误与 SQLite 数据库被锁定有关。 However, (I don't think) I am using any such database.但是,(我不认为)我正在使用任何此类数据库。 I don't use sqflite;我不使用 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.编辑 #2:我已经更新了上面的代码片段以包含我正在测试的整个代码。 As you can see, I don't explicitly initialize SQL database anywhere.如您所见,我没有在任何地方显式初始化 SQL 数据库。 So, it is likely that Firebase.initializeApp uses/locks SQL database internally.因此,Firebase.initializeApp 很可能在内部使用/锁定 SQL 数据库。 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."由于我多次调用 Firebase.initializeApp——所有这些调用都将访问同一个 SQL 数据库——这些调用相互阻塞,导致错误“SQL 数据库被锁定”。

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.如果确实如此,如果我等待每个 Firebase.initializeApp 调用完成,然后再为下一个 Firebase 项目调用它,错误应该会消失。

This is my current theory, and I am working to modify my code initialize my firebase projects one after the other.这是我目前的理论,我正在努力修改我的代码,一个接一个地初始化我的 firebase 项目。

Solution: My theory (mentioned in "Edit #2") was valid.解决方案:我的理论(在“编辑#2”中提到)是有效的。 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 谢谢

It's not a firebase issue, it's an SQFlite issue.这不是 Firebase 问题,而是 SQFlite 问题。 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.如果您正在获取大量数据,导致对 SQFlite 数据库的大量写入,则会导致它锁定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM