Flutter, sqflite, and escaping quotes with SQL INSERT and UPDATE statements

When you want to do a SQL INSERT or UPDATE using the Flutter sqflite package, I’ve found that it’s best to use their insert and update methods (as shown below) so you don’t have to worry about escaping single- and double-quotes. This is similar to the reason why Java developers switched from Statement to PreparedStatement way back in the late 1900s.

As an example, given this SQLite database table:

CREATE TABLE quotes (
    id INTEGER PRIMARY KEY,
    quote TEXT UNIQUE,
    author TEXT,
    date_last_touched INTEGER DEFAULT (cast(strftime('%s','now') as int))
)

After I ran into problems with single- and double-quotes using SQL INSERT statements, I switched to using the sqflite insert method:

// INSERT (return the insert id)
Future<int> insertQuote(Quote quote) async {
    Database db = await instance.database;
    var row = {
        'quote'  : quote.quote,
        'author' : quote.author
    };
    return await db.insert('quotes', row);
}

Similarly, I switched to using the sqflite update method like this:

// UPDATE (returns the number of rows updated)
Future<int> updateQuote(Quote quote) async {
    Database db = await instance.database;

    var row = {
        'id'     : quote.id,
        'quote'  : quote.quote,
        'author' : quote.author
    };

    return await db.update(
        'quotes', 
        row,
        where: 'id = ?', 
        whereArgs: [quote.id]
    );
}

Assuming that you know how to get access to a sqflite Database instance, I hope the rest of this code makes sense.

In summary, if you wanted to see how to write a SQL INSERT and UPDATE statements using the Flutter sqflite package, while also handling the escaping of single-quotes and double-quotes, I hope this example is helpful.

A complete sqflite database helper example

If it helps to add a little more context to this solution, here’s the complete source code for a Flutter/sqflite “datebase helper” class I created:

import 'dart:io';
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'data_model.dart';

class DatabaseHelper {
    
    static final _databaseName = "JustBe.db";
    static final _databaseVersion = 1;

    // make this a singleton class
    DatabaseHelper._privateConstructor();
    static final DatabaseHelper instance = DatabaseHelper._privateConstructor();

    // only have a single app-wide reference (getter) to the database
    static Database _database;
    Future<Database> get database async {  //GETTER
        if (_database != null) return _database;
        // lazily instantiate the db the first time it is accessed
        _database = await _initDatabase();
        return _database;
    }

    // this opens the database (and creates it if it doesn’t exist)
    _initDatabase() async {
        Directory documentsDirectory = await getApplicationDocumentsDirectory();
        String path = join(documentsDirectory.path, _databaseName);
        Database foo = await openDatabase(
            path,
            version: _databaseVersion,
            onOpen: (instance) {},
            onCreate: _onCreate
        );
        return foo;
    }

    // SQL code to create the database table
    Future _onCreate(Database db, int version) async {
        // note that the date/time field is stored as an integer because
        // that’s all you can do with SQLite. so this field is the Unix
        // epoch time.
        var createString = '''
            CREATE TABLE quotes (
                id INTEGER PRIMARY KEY,
                quote TEXT UNIQUE,
                author TEXT,
                date_last_touched INTEGER DEFAULT (cast(strftime('%s','now') as int))
            )
        ''';
        await db.execute(createString);

        //TODO do i need `await` before each of these?
        db.execute("insert into quotes (quote, author) values ('Be happy in the moment, that’s enough. Each moment is all we need, not more.', 'Mother Teresa')");
        db.execute("insert into quotes (quote, author) values ('Be here now', 'Ram Dass')");
        db.execute("insert into quotes (quote, author) values ('Do every act of your life as though it were the very last act of your life.', 'Marcus Aurelius')");
        // many more INSERTs here ...
    }
    

    /// INSERT
    Future<int> insertQuote(Quote quote) async {
        Database db = await instance.database;
        var row = {
            'quote'  : quote.quote,
            'author' : quote.author
        };
        return await db.insert('quotes', row);  //the id
    }

    /// SELECT ALL
    Future<List<Quote>> selectAllQuotes() async {
        Database db = await instance.database;
        var result = await db.rawQuery('SELECT * FROM quotes ORDER BY date_last_touched DESC');
        var quotes = result.map((qAsMap) => Quote.fromMap(qAsMap));
        return quotes.toList();
    }

    /// SELECT ALL BY ID
    Future<List<int>> selectAllQuoteIds() async {
        Database db = await instance.database;
        var result = await db.rawQuery('SELECT id FROM quotes');
        var ids = result.map((qAsMap) => _getIdFromMap(qAsMap));
        return ids.toList();
    }

    int _getIdFromMap(Map<String, dynamic> qMap) {
        return qMap['id'];
    }

    /// NOTE: can return `null`
    Future<Quote> selectQuote(int id) async {
        Database db = await instance.database;
        var results = await db.rawQuery('SELECT * FROM quotes WHERE id = $id');
        if (results.length > 0) {
            return Quote.fromMap(results.first);
        }
        return null;
    }

    /// UPDATE. Returns the number of rows updated.
    Future<int> updateQuote(Quote quote) async {
        Database db = await instance.database;

        var row = {
            'id'     : quote.id,
            'quote'  : quote.quote,
            'author' : quote.author
        };

        // returns num rows updated
        return await db.update(
            'quotes', 
            row,
            where: 'id = ?', 
            whereArgs: [quote.id]
        );
    }

    /// DELETE
    Future<int> deleteQuote(int id) async {
        Database db = await instance.database;
        return await db.rawDelete('DELETE FROM quotes WHERE id = $id');
    }

    Future<int> rowCount() async {
        debugPrint('ROW COUNT CALLED');
        Database db = await instance.database;
        return Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(1) FROM quotes'));
    }

}

I don’t know if all of that code is 100% correct (or best practices), but I can confirm that it works, especially the SQL INSERT and UPDATE code.