![](/img/trans.png)
[英]Multiple Firebase Projects in one app with different persistence setup
[英]Integrating multiple Firebase projects in one Flutter app causes app to crash
背景:我正在尝试开发一个连接 3 个不同 Firebase 项目的 Flutter 应用程序。 我遵循 FlutterFire 上的说明,特别是FlutterFire 概述和核心 | FlutterFire并在 App 内初始化了 3 个firebase项目应用程序。
注意:出于合法性原因,我已经使用了 3 个不同的 Firebase 项目,并且我不能使用口味。 此外,该应用程序使用共享首选项在本地存储数据。
问题:当我启动模拟器并运行应用程序时,一切似乎都正常。 该应用程序启动,连接到所有三个 firebase 项目并执行我想要的所有操作。 但是,现在如果我尝试重新运行应用程序(即停止应用程序,然后按绿色箭头重新启动它),应用程序崩溃并显示错误消息:
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.
我只有在初始化多个 firebase 项目并重新启动应用程序(不重新启动模拟器)时才会收到此错误。
你知道是什么导致了这个错误以及如何修复它吗?
代码:
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");
}
);
}
}
产生错误的步骤:
附加信息:
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
当我第一次启动应用程序时,我绝对没有错误,所有三个项目都可以访问各自的 Cloud Firestores 和 Firebase Auth。
此外,我正在 Android Studio 提供的 Pixel 2 (API 29) 模拟器上测试我的应用程序。
我一直在网上搜索解决方案,但大多数人在使用启用持久性的 Firebase 时遇到此错误(例如: Firebase 数据库崩溃 SQLiteDatabaseLockedException ) - 我没有使用。
我尝试禁用共享首选项,但无济于事。 执行重新启动时,应用程序仍然崩溃。
我的假设:我有一种预感,当我重新运行该应用程序时,它会重新构建App
,从而重新初始化导致错误的三个 firebase 项目。 我试图通过在App
的initializeState()
方法中放置一个打印来验证这个理论,以跟踪App
何时重新构建。 果然,只要多次调用initializeState()
,我的应用就会崩溃。
但我不知道如何“告诉”应用程序不要重新初始化项目。
编辑 #1:我知道错误与 SQLite 数据库被锁定有关。 但是,(我不认为)我正在使用任何此类数据库。 我不使用 sqflite; 相反,我的应用程序使用共享首选项在本地保留数据(因为它只保留少量数据),仅此而已。
编辑 #2:我已经更新了上面的代码片段以包含我正在测试的整个代码。 如您所见,我没有在任何地方显式初始化 SQL 数据库。 因此,Firebase.initializeApp 很可能在内部使用/锁定 SQL 数据库。 由于我多次调用 Firebase.initializeApp——所有这些调用都将访问同一个 SQL 数据库——这些调用相互阻塞,导致错误“SQL 数据库被锁定”。
如果确实如此,如果我等待每个 Firebase.initializeApp 调用完成,然后再为下一个 Firebase 项目调用它,错误应该会消失。
这是我目前的理论,我正在努力修改我的代码,一个接一个地初始化我的 firebase 项目。
解决方案:我的理论(在“编辑#2”中提到)是有效的。 我如上所述修改了我的代码,所有错误都消失了。
这是我完整的,带注释的工作代码。
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(),
);
}
}));
}
}
这不是 Firebase 问题,而是 SQFlite 问题。 错误说:
Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
E/AndroidRuntime(16200)
它与初始化应用程序后代码中发生的事情有关。 如果您正在获取大量数据,导致对 SQFlite 数据库的大量写入,则会导致它锁定。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.