Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] attempt to write a readonly database #1137

Open
haashem opened this issue Oct 27, 2024 · 2 comments
Open

[Android] attempt to write a readonly database #1137

haashem opened this issue Oct 27, 2024 · 2 comments

Comments

@haashem
Copy link

haashem commented Oct 27, 2024

Hi,

I use sqflite to cache network responses, and delete the expired caches weekly.
After publishing the app, I got below crash reports in Firebase for some Android devices:

Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError
DatabaseException(attempt to write a readonly database (code 1032 SQLITE_READONLY_DBMOVED[1032])) sql 'INSERT OR REPLACE INTO network_cache ("key", response, timestamp) VALUES (?, ?, ?)' args [https://api.myapp.com/api/app?platform=a..., {"request":{"method":"HttpMethod.get","url":"https..., 2024-10-26T03:56:12.376333]

Screenshot 2024-10-27 at 11 32 12

Here is how I create the db:

static Future<SqliteCacheDatabase> init() async {
    final dbDirectory = await getApplicationCacheDirectory();
    dbDirectory.create(recursive: true);
    final db = await openDatabase(
      join(dbDirectory.path, 'sqflite_cache.db'),
      onCreate: (db, version) {
        return db.execute('''
        CREATE TABLE $networkCacheTableName(
          key TEXT PRIMARY KEY,
          response TEXT,
          timestamp TEXT
        )
      ''');
      },
      version: 1,
    );
    return SqliteCacheDatabase._(db);
  }

I don't delete the db file after creation.

Do you think it can be result of parallel writes in the same table?

  @override
  Future<void> insert(CachedResponse cachedResponse, {required String key}) =>
      _database.instance.insert(
        SqliteCacheDatabase.networkCacheTableName,
        {
          'key': key,
          'response': jsonEncode(cachedResponse.response),
          'timestamp': cachedResponse.timestamp.toIso8601String(),
        },
        conflictAlgorithm: ConflictAlgorithm.replace,
      );

@alextekartik
Copy link
Contributor

sqflite on iOS and Android uses whatever SQLite bundled on the platform. It seems the UPSERT (ON CONFLICT) feature was added in SQLite 3.24 which might not be present in some Android version: See

You can do a "SELECT sqlite_version()" to check the version.

I see 2 solutions:

  • Do not use on CONFLICT and instead use transaction to catch the error on the insert and then perform the update
  • Try sqflite_common_ffi along with sqlite3 and sqlite3 flutter libs. I have not tried it much but it should work

@alextekartik
Copy link
Contributor

Forget that, I see the report is on Android 14 which should be fine. Parallel writes should be fine. sqflite serializes calls anyway. Still puzzled...my only "feeling" would be not to use the cache directory but instead use getDatabasesPath on Android and manage on your side to not make it too big. I don't know what is the Android behavior as when the cache is cleared (low storage situation, mabye your database is very big) but indeed if Android decides to delete the files, the error reported might happen. One alternative solution would be to catch this error and re-open the database when it happens. I'll try later (not very soon) to reproduce the issue by filling a db on the cache or by forcing file deletion but so far sqlite does not provide any automatic mechanism to handle this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants