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:
- Start the emulator and run the app by pressing the green arrow button
- After the app is up and running, press the red button to stop execution.
- Restart the app by pressing the green arrow button
Additional information:
- 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
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.
Also, I am testing my app on the Pixel 2 (API 29) emulator provided by Android Studio.
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.
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