pomako3212tuli
@pomako3212tuli

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

データベースが読み込めない

解決したいこと

Flutterで単語帳のスマホアプリを作っています。
Driftを使って、データベースの操作をしています。
データベースを読み込みたいのですが、エラーが出ているので、その解決方法を教えてほしいです。

発生している問題・エラー

SqliteException(11): while executing, database disk image is malformed, database disk image is malformed (code 11)
Causing statement: CREATE TABLE IF NOT EXISTS "words" ("id" INTEGER NOT NULL, "sort" TEXT NOT NULL, "name" TEXT NOT NULL, "english" TEXT NOT NULL, "rank" INTEGER NOT NULL, "read" TEXT NOT NULL, "start" TEXT NOT NULL, "end" TEXT NOT NULL, "nerve" TEXT NOT NULL, "medulla" TEXT NOT NULL, "action" TEXT NOT NULL, "sub" TEXT NOT NULL, "comment" TEXT NOT NULL, PRIMARY KEY ("name"));, parameters:

wordlist.dartの

void _getAllWords() async{
    _wordList = await database.allWords;
    setState(() {});
  }

という箇所で、エラー文が出ます。データベースの中身を、画面に表示したいと思って、これを書いているのですが…。
エミュレータを起動して、デバッグすると、アプリは立ち上がるのですが、途中でエラーが出てしまい、画面上にデータベースの中身は表示されません。

該当するソースコード

main.dart

//一番基本の土台画面。

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:kinnikutango/db/database.dart';
import 'screens/app.dart';
import 'package:path/path.dart';//手入力付けたし
import 'package:path_provider/path_provider.dart';//手入力付けたし

late MyDatabase database;

void main() async{
  WidgetsFlutterBinding.ensureInitialized();
  var dbPath = await getDbPath();
  database = MyDatabase(dbPath: dbPath);

  runApp(MyApp());
}

Future<String> getDbPath() async{
  var dbDir = await getApplicationDocumentsDirectory();
  var dbPath = join(dbDir.path, "word.db");
  
  if(FileSystemEntity.typeSync(dbPath) == FileSystemEntityType.notFound){
    ByteData byteData = await rootBundle.load("assets/db/word.db");
    List<int> bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes);
    await File(dbPath).writeAsBytes(bytes);
  }
  return dbPath;
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "筋肉単語", //TODO  後でタイトル変更する
      home: AppScreen(),
      theme: ThemeData(
        fontFamily: "NotoSansJapanese",//フォント変更
        appBarTheme: AppBarTheme(
          backgroundColor: Color(0xFF96BBFF),
          centerTitle: true,
        ),
      ),
      debugShowCheckedModeBanner: false,//デバッグの右上のリボンみたいなのを消す
    );
  }
}

wordlist.dart

import 'package:flutter/material.dart';
import '../db/database.dart';
import '../main.dart';
import 'edit_screen.dart';

class WordListScreen extends StatefulWidget {
  const WordListScreen({Key? key}) : super(key: key);

  @override
  State<WordListScreen> createState() => _WordListScreenState();
}

class _WordListScreenState extends State<WordListScreen> {

  List<Word> _wordList = [];

  @override
  void initState() {
    super.initState();
    _getAllWords();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('単語一覧'),
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () => _addNewWord(),
            tooltip: "新しい単語の登録", //長押しすると説明文を出す
          ),
        ],
      ),
      body: _wordListWidget(),
    );
  }

  _addNewWord() {
    Navigator.pushReplacement(
        context, MaterialPageRoute(builder: (context) => EditScreen()));
  }

  void _getAllWords() async{
    _wordList = await database.allWords;
    setState(() {});
  }

  Widget _wordListWidget() {
    return ListView.builder(
      itemCount: _wordList.length,
        itemBuilder: (context, int position) => _wordItem(position));
  }

  Widget _wordItem(int position) {
    return Card(
      child: ListTile(
        title: Text(
          "${_wordList[position].name}"
        ),
        subtitle: Text(
          "${_wordList[position].read}"
        ),
      ),
    );
  }
}

database.dart

import 'dart:io';

import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
import 'package:sqlite3/sqlite3.dart';

part 'database.g.dart';

class Words extends Table {
  IntColumn get id => integer()();

  TextColumn get sort => text().nullable()();

  TextColumn get name => text()();

  TextColumn get english => text().nullable()();

  IntColumn get rank => integer().nullable()();

  TextColumn get read => text().nullable()();

  TextColumn get start => text().nullable()();

  TextColumn get end => text().nullable()();

  TextColumn get nerve => text().nullable()();

  TextColumn get medulla => text().nullable()();

  TextColumn get action => text().nullable()();

  TextColumn get sub => text().nullable()();

  TextColumn get comment => text().nullable()();

  @override
  Set<Column> get primaryKey => {name};
}

@DriftDatabase(tables: [Words])
class MyDatabase extends _$MyDatabase {
  final String dbPath;

  MyDatabase({required this.dbPath})
      : super(_openConnection(dbPath)); //null許容のエラーが出たから、「required」追加した。

  @override
  int get schemaVersion => 1;

  //Creates
  Future addWord(Word word) => into(words).insert(word);

  //Read
  Future<List<Word>> get allWords => select(words).get();

  //Update
  Future updateWord(Word word) => update(words).replace(word);

  //Delete
  Future deleteWord(Word word) =>
      (delete(words)..where((t) => t.name.equals(word.name))).go();
}

LazyDatabase _openConnection(String dbPath) {
  // the LazyDatabase util lets us find the right location for the file async.
  return LazyDatabase(() async {
    // put the database file, called db.sqlite here, into the documents folder
    // for your app.
    //final dbFolder = await getApplicationDocumentsDirectory();//本当にいらんのか不明。
    final file = File(dbPath);

    // Also work around limitations on old Android versions
    if (Platform.isAndroid) {
      await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
    }

    // Make sqlite3 pick a more suitable location for temporary files - the
    // one from the system may be inaccessible due to sandboxing.
    final cachebase = (await getTemporaryDirectory()).path;
    // We can't access /tmp on Android, which sqlite3 would try by default.
    // Explicitly tell it about the correct temporary directory.
    sqlite3.tempDirectory = cachebase;

    return NativeDatabase.createInBackground(file);
  });
}


自分で試したこと

・エラー文をgoogleで検索してみたけど、いまいち分かりませんでした。
・driftの自動生成コード前からやり直し。
・最初はデータベースにnullがあったので、database.dartの、wordsテーブルで、nullableで書き直して、自動生成コードをやり直しました。
・データベースでnullのところを、「0」に置き換えてみて、自動生成コードをやり直し。
・その他、チャットgptに言われた通りにやってみましたが、私には何をしていたのかは不明でした…。言われた通りに続けていっても、次から次へとエラーが出ました。

ここまでお読みいただき、ありがとうございます。
プログラミング自体、かなりの初心者で、ここで質問させていただくのも初めてなので、質問が分かりにくかったら申し訳ございません。
よろしくお願いします。

0

2Answer

Comments

  1. @pomako3212tuli

    Questioner

    ご返信ありがとうございます。
    申し訳ございません、ご指摘の箇所が分からなくて…
    コードを何度か見直したのですが、合っている「ような気がする」ので、データベース自体のテーブルの名前のことかと思ったのですが、合っていますでしょうか?
    テーブルは「words」にしています…。
    やはり、コードのどこかが間違って「word」になっているのでしょうか?
    知識不足すぎて、申し訳ございません。

  2.  var dbPath = join(dbDir.path, "word.db");
    
       ByteData byteData = await rootBundle.load("assets/db/word.db");
    

    掲示されたコードにwordがあります。違っていたらごめんなさい。

  3. @pomako3212tuli

    Questioner

    ご返信ありがとうございます。
    そこの部分は、word.dbでも問題ないと書いてあったと思ったのですが、ダメなのかもしれません!
    ご指摘いただき、ありがとうございます。
    その部分を一度修正してみようかと思います。

  4. @pomako3212tuli

    Questioner

    nak435さん!
    ご指摘いただいた、word.dbをwords.dbに変えたら解決いたしました。
    一週間くらい悩んでいたので、本当に嬉しかったです。
    ご回答いただき、ありがとうございました!

  5. 解決してよかったですね ✌️

「データベースのディスクイメージが不正です」
めちゃくちゃ怖いこと書いてありますね。

気になることとして、getDBPath()でDBに直接ファイル書き込みをしているようですが、これはなんでしょうか?
データベースファイル、触っちゃダメですよ…?

あと余談ですが、こういう「主目的以外の処理がついでに入っている関数」は「副作用のある関数」と言って、厄介なバグの温床になります。
getPath()であれば、パスの取得だけを行い、他の事をしたければ関数の外に続けて書くのがいいです。

1Like

Comments

  1. @pomako3212tuli

    Questioner

    ご返信ありがとうございます。

    「データベースが不正」とは書いてあるのですが、db browser for sqliteで開いてみると、ちゃんと開けるのです…
    私は訳がわからなくて、怖いです。

    「getDBPath()でDBに直接ファイル書き込みをしているようです」、「データベースファイル、触っちゃダメですよ…?」ってそうなのですか!?
    実はある講座を参考に進めていて、その通りに書いている「つもり」なので、いまいち理解出来ていないのですが、初めてアプリを開いたときにはデータベースをassetsフォルダからコピーしてきて、既にデータベースがある時はassetsフォルダはスルーして、そのまま既にあるものを使う、とおっしゃっていたと思っています。すみません、自信がないです…。

    「バグの温床」の話もなんか聞いているだけで怖いのですが、何をどう書き直したら良いのかわからないです。(たぶん、教えてくださっているのだと思うのですが)

    すみません、知識がないばかりに、せっかく教えていただいたことが生かせなさそうです…

Your answer might help someone💌