From 1a9ffd4b90ae9e7aeedf25592274751ac9c21e0f Mon Sep 17 00:00:00 2001 From: Matt Hamann Date: Tue, 24 Feb 2026 09:53:59 -0500 Subject: [PATCH 1/2] fix(state): prevent multiple DataStore instances for same file Fixes critical bug where defaultDataStore() creates new DataStore instances on every call instead of returning the singleton. This caused fatal IllegalStateException crashes in React Native apps where multiple DataStores were active for rownd_state.json. Root cause: DataStoreFactory.create() result was not assigned to the companion object's dataStore variable, breaking the singleton pattern. Fixes: Multiple DataStores active for the same file exception --- .../src/main/java/io/rownd/android/models/repos/StateRepo.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt b/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt index 27aab95..aa4bb10 100644 --- a/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt +++ b/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt @@ -207,7 +207,7 @@ class StateRepo @Inject constructor() { return it } - return DataStoreFactory.create( + dataStore = DataStoreFactory.create( storage = FileStorage(GlobalStateSerializer) { context.dataStoreFile(Rownd.config.stateFileName) }, @@ -225,6 +225,7 @@ class StateRepo @Inject constructor() { return@ReplaceFileCorruptionHandler GlobalState() } ) + return dataStore!! } } } From eb3ab098f914833be4612340008b81d250674fc8 Mon Sep 17 00:00:00 2001 From: Matt Hamann Date: Tue, 24 Feb 2026 10:02:13 -0500 Subject: [PATCH 2/2] fix: address thread safety and memory leak concerns - Add @Synchronized annotation to prevent race conditions where multiple threads could create separate DataStore instances - Store result in local variable and return directly to avoid !! assertion - Use applicationContext to prevent memory leaks from short-lived contexts Addresses Sourcery review feedback on thread safety and best practices. --- .../main/java/io/rownd/android/models/repos/StateRepo.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt b/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt index aa4bb10..28a250b 100644 --- a/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt +++ b/android/src/main/java/io/rownd/android/models/repos/StateRepo.kt @@ -201,15 +201,17 @@ class StateRepo @Inject constructor() { companion object { private var dataStore: DataStore? = null + + @Synchronized fun defaultDataStore(context: Context): DataStore { // DataStore must be a singleton dataStore?.let { return it } - dataStore = DataStoreFactory.create( + val ds = DataStoreFactory.create( storage = FileStorage(GlobalStateSerializer) { - context.dataStoreFile(Rownd.config.stateFileName) + context.applicationContext.dataStoreFile(Rownd.config.stateFileName) }, corruptionHandler = ReplaceFileCorruptionHandler { ex -> // Handle cases where on-device state has become corrupt. @@ -225,7 +227,8 @@ class StateRepo @Inject constructor() { return@ReplaceFileCorruptionHandler GlobalState() } ) - return dataStore!! + dataStore = ds + return ds } } }