LoginSignup
29
31

More than 3 years have passed since last update.

【Flutter】SQLiteでデータの永続化をした簡単メモアプリ

Posted at

この記事は「HTC Advent Calendar 2020」の16日目の記事です。
今回は、FlutterでSQLiteを利用したデータのやりとりについて書いていきます。
簡単なメモアプリをつくってデータの永続化ができることを確認します。

前提

こちらを参考にしながら、実際に自分でやってみたという内容になります。
優しく、手取り足取り教えるチュートリアル的な記事ではないので、その辺はご理解いただければと思います。

対象者

  • Flutterを使ったことがある
  • 簡単なSQLがわかる

ざっくりとしていますが、Flutter? SQL? 何それ、美味しいの?って状態じゃなければ問題ないかと思います。

開発環境

macOS Catalina 10.15.7
Visual Studio Code 1.52.0

$  flutter --version
Flutter 1.24.0-10.2.pre • channel beta • https://github.com/flutter/flutter.git
Tools • Dart 2.12.0 (build 2.12.0-29.10.beta)

目指すゴール

作成したFlutterアプリ上で以下のようなデータのやりとりができる。
1. テーブルの作成
2. データの挿入
3. データの取得
4. データの更新
5. データの削除

※今回はあくまで「データのやりとり」についてなので、アプリのレイアウトや細かいwidgetの説明は省きます

成果物イメージ

output_demo2.gif

開発手順

SQLiteの導入

SQLite

簡単に説明すると、サーバーとしてデータベースを用意するわけではなく、アプリケーションに組み込んで利用するデータベースになります。
細かい話はぐぐってみてください。

「sqflite」パッケージの導入

プラグインを追加するために、pubspec.ymlを編集します。
sqflite:SQLiteデータベースとやりとりが出来る様になります。
path: データベースを格納するパスを扱うパッケージになります。

pubspec.yml
dependencies:
  flutter:
    sdk: flutter
  sqflite:
  path:

プラグインを追加したら、使用するためにインポートします。
dart:main.dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

データモデルの定義

main.dart
class Memo {
  final int id;
  final String text;

  Memo({this.id, this.text});

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'text': text,
    };
  }
}

データベース接続/テーブル作成

main.dart
class Memo {
// ... 省略
  static Future<Database> get database async {
    // openDatabase() データベースに接続
    final Future<Database> _database = openDatabase(
      // getDatabasesPath() データベースファイルを保存するパス取得
      join(await getDatabasesPath(), 'memo_database.db'),
      onCreate: (db, version) {
        return db.execute(
          // テーブルの作成
          "CREATE TABLE memo(id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT)",
        );
      },
      version: 1,
    );
    return _database;
  }
}

データの挿入

main.dart
class Memo {
// ... 省略
static Future<Database> get database async {
  // ... 省略
  }

  static Future<void> insertMemo(Memo memo) async {
    final Database db = await database;
    await db.insert(
      'memo',
      memo.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }
}

データの取得

main.dart
class Memo {
// ... 省略
static Future<Database> get database async {
  // ... 省略
  }
  static Future<void> insertMemo(Memo memo) async {
  // ... 省略
  }
  static Future<List<Memo>> getMemos() async {
    final Database db = await database;
    final List<Map<String, dynamic>> maps = await db.query('memo');
    return List.generate(maps.length, (i) {
      return Memo(
        id: maps[i]['id'],
        text: maps[i]['text'],
      );
    });
  }
}

データの更新

main.dart
class Memo {
// ... 省略
static Future<Database> get database async {
  // ... 省略
  }
  static Future<void> insertMemo(Memo memo) async {
  // ... 省略
  }
  static Future<List<Memo>> getMemos() async {
  // ... 省略
  }
  static Future<void> updateMemo(Memo memo) async {
    // Get a reference to the database.
    final db = await database;
    await db.update(
      'memo',
      memo.toMap(),
      where: "id = ?",
      whereArgs: [memo.id],
      conflictAlgorithm: ConflictAlgorithm.fail,
    );
  }
}

データの削除

main.dart
class Memo {
// ... 省略
static Future<Database> get database async {
  // ... 省略
  }
  static Future<void> insertMemo(Memo memo) async {
  // ... 省略
  }
  static Future<List<Memo>> getMemos() async {
  // ... 省略
  }
  static Future<void> updateMemo(Memo memo) async {
  // ... 省略
  }
  static Future<void> deleteMemo(int id) async {
    final db = await database;
    await db.delete(
      'memo',
      where: "id = ?",
      whereArgs: [id],
    );
  }
}

定義した関数の使用方法

ボタンを押したタイミングや、initState()などで実行しています。

例えばこんな感じです。

RaisedButton(
  onPressed: () async {
  Memo _memo = Memo(text: 'じゃむおじさん');
  await Memo.insertMemo(_memo);
  },
),

データの取得、更新、削除も同様にして実行することができます。

最後に

SQLiteを使って簡単にデータのやりとりができることが確認できました。
簡単に実装できるけど、Firebaseの方が多機能なので、あまり出番はないのかなと思っています。
使うとしたら、リアルタイム通信を必要としない、ローカルにデータを保存したい場合などですかね。

今回作成したソースコードはこちらに記載しておきます

ソースコード
main.dart
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'package:path/path.dart';

class Memo {
  final int id;
  final String text;

  Memo({this.id, this.text});

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'text': text,
    };
  }

  @override
  String toString() {
    return 'Memo{id: $id, text: $text}';
  }

  static Future<Database> get database async {
    final Future<Database> _database = openDatabase(
      join(await getDatabasesPath(), 'memo_database.db'),
      onCreate: (db, version) {
        return db.execute(
          "CREATE TABLE memo(id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT)",
        );
      },
      version: 1,
    );
    return _database;
  }

  static Future<void> insertMemo(Memo memo) async {
    final Database db = await database;
    await db.insert(
      'memo',
      memo.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  static Future<List<Memo>> getMemos() async {
    final Database db = await database;
    final List<Map<String, dynamic>> maps = await db.query('memo');
    return List.generate(maps.length, (i) {
      return Memo(
        id: maps[i]['id'],
        text: maps[i]['text'],
      );
    });
  }

  static Future<void> updateMemo(Memo memo) async {
    final db = await database;
    await db.update(
      'memo',
      memo.toMap(),
      where: "id = ?",
      whereArgs: [memo.id],
      conflictAlgorithm: ConflictAlgorithm.fail,
    );
  }

  static Future<void> deleteMemo(int id) async {
    final db = await database;
    await db.delete(
      'memo',
      where: "id = ?",
      whereArgs: [id],
    );
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo SQL',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MySqlPage(),
    );
  }
}

class MySqlPage extends StatefulWidget {
  @override
  _MySqlPageState createState() => _MySqlPageState();
}

class _MySqlPageState extends State<MySqlPage> {
  List<Memo> _memoList = [];
  final myController = TextEditingController();
  final upDateController = TextEditingController();
  var _selectedvalue;

  Future<void> initializeDemo() async {
    _memoList = await Memo.getMemos();
  }

  @override
  void dispose() {
    myController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('メモアプリ'),
      ),
      body: Container(
        padding: EdgeInsets.all(32),
        child: FutureBuilder(
          future: initializeDemo(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              // 非同期処理未完了 = 通信中
              return Center(
                child: CircularProgressIndicator(),
              );
            }
            return ListView.builder(
              itemCount: _memoList.length,
              itemBuilder: (context, index) {
                return Card(
                  child: ListTile(
                    leading: Text(
                      'ID ${_memoList[index].id}',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    title: Text('${_memoList[index].text}'),
                    trailing: SizedBox(
                      width: 76,
                      height: 25,
                      child: RaisedButton.icon(
                        onPressed: () async {
                          await Memo.deleteMemo(_memoList[index].id);
                          final List<Memo> memos = await Memo.getMemos();
                          setState(() {
                            _memoList = memos;
                          });
                        },
                        icon: Icon(
                          Icons.delete_forever,
                          color: Colors.white,
                          size: 18,
                        ),
                        label: Text(
                          '削除',
                          style: TextStyle(fontSize: 11),
                        ),
                        color: Colors.red,
                        textColor: Colors.white,
                      ),
                    ),
                  ),
                );
              },
            );
          },
        ),
      ),
      floatingActionButton: Column(
        verticalDirection: VerticalDirection.up,
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: () {
              showDialog(
                  context: context,
                  builder: (_) => AlertDialog(
                        title: Text("新規メモ作成"),
                        content: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: <Widget>[
                            Text('なんでも入力してね'),
                            TextField(controller: myController),
                            RaisedButton(
                              child: Text('保存'),
                              onPressed: () async {
                                Memo _memo = Memo(text: myController.text);
                                await Memo.insertMemo(_memo);
                                final List<Memo> memos = await Memo.getMemos();
                                setState(() {
                                  _memoList = memos;
                                  _selectedvalue = null;
                                });
                                myController.clear();
                                Navigator.pop(context);
                              },
                            ),
                          ],
                        ),
                      ));
            },
          ),
          SizedBox(height: 20),
          FloatingActionButton(
              child: Icon(Icons.update),
              backgroundColor: Colors.amberAccent,
              onPressed: () async {
                await showDialog(
                    context: context,
                    builder: (BuildContext context) {
                      return AlertDialog(
                        content: StatefulBuilder(
                          builder:
                              (BuildContext context, StateSetter setState) {
                            return Column(
                              mainAxisSize: MainAxisSize.min,
                              children: <Widget>[
                                Text('IDを選択して更新してね'),
                                Row(
                                  children: <Widget>[
                                    Flexible(
                                      flex: 1,
                                      child: DropdownButton(
                                        hint: Text("ID"),
                                        value: _selectedvalue,
                                        onChanged: (newValue) {
                                          setState(() {
                                            _selectedvalue = newValue;
                                            print(newValue);
                                          });
                                        },
                                        items: _memoList.map((entry) {
                                          return DropdownMenuItem(
                                              value: entry.id,
                                              child: Text(entry.id.toString()));
                                        }).toList(),
                                      ),
                                    ),
                                    Flexible(
                                      flex: 3,
                                      child: TextField(
                                          controller: upDateController),
                                    ),
                                  ],
                                ),
                                RaisedButton(
                                  child: Text('更新'),
                                  onPressed: () async {
                                    Memo updateMemo = Memo(
                                        id: _selectedvalue,
                                        text: upDateController.text);
                                    await Memo.updateMemo(updateMemo);
                                    final List<Memo> memos =
                                        await Memo.getMemos();
                                    super.setState(() {
                                      _memoList = memos;
                                    });
                                    upDateController.clear();
                                    Navigator.pop(context);
                                  },
                                ),
                              ],
                            );
                          },
                        ),
                      );
                    });
              }),
        ],
      ),
    );
  }
}

29
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
31