Как я использовал SQLite в своем приложении Flutter с sqflite

Как я использовал SQLite в своем приложении Flutter с sqflite

FlutterPulse

Эта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Раота с локальным хранилищем в Flutter? Если вы хотите сохранять данные, такие как заметки или задачи, без использования интернета, SQLite - отличный вариант…

Раота с локальным хранилищем в Flutter? Если вы хотите сохранять данные, такие как заметки или задачи, без использования интернета, SQLite - отличный вариант.

Это на самом деле моя первая публикация на Medium, и я хотел поделиться чем-то простым, но полезным. В этом руководстве я покажу вам, как использовать пакет sqflite для настройки локальной базы данных и выполнения базовых операций CRUD. Давайте приступим!

Настройка проекта

Перед тем, как приступить к коду, нам нужно добавить необходимые пакеты и настроить структуру.

Добавление зависимостей

Откройте ваш pubspec.yaml и добавьте следующее:

зависимости:
 flutter:
 sdk: flutter
 sqflite: ^2.3.2
 path: ^1.8.3

Затем запустите:

flutter pub get в терминале

Предлагаемая структура папок

Вы можете организовать свой проект так, чтобы все было чисто:

lib/
── db/
│ ── database_helper.dart
── models/
│ ── note.dart
── screens/
│ ── home_screen.dart
── main.dart

Создание модели Note

Давайте сделаем все просто с 3 полями: id, title, и content.

Файл: lib/models/note.dart

класс Note {
 final int? id;
 final String title;
 final String content;
Note({this.id, required this.title, required this.content});
// Преобразование объекта Note в Map
 Map<String, dynamic> toMap() {
 return {
 'id': id,
 'title': title,
 'content': content,
 };
 }
// Преобразование Map в объект Note
 factory Note.fromMap(Map<String, dynamic> map) {
 return Note(
 id: map['id'],
 title: map['title'],
 content: map['content'],
 );
 }
}

Эта модель дает нам два полезных метода:

  • toMap() → для сохранения в SQLite.
  • fromMap() → для чтения из SQLite.

Настройка SQLite с DatabaseHelper

Этот класс будет обрабатывать:

  • Создание базы данных
  • Определение таблицы
  • Вставка, обновление, удаление и получение заметок

Файл: lib/db/database_helper.dart

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/note.dart';

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  factory DatabaseHelper() => _instance;
  DatabaseHelper._internal();

  static Database? _database;

  Future<Database> get database async {
    _database??= await _initDB();
    return _database!;
  }

  Future<Database> _initDB() async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, 'notes.db');

    return await openDatabase(
      path,
      version: 1,
      onCreate: _createDB,
    );
  }

  Future _createDB(Database db, int version) async {
    await db.execute('''
      CREATE TABLE notes (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT NOT NULL
      )
    ''');
  }

  // Вставка заметки
  Future<int> insertNote(Note note) async {
    final db = await database;
    return await db.insert('notes', note.toMap(), conflictAlgorithm: ConflictAlgorithm.replace);
  }

  // Получение всех заметок
  Future<List<Note>> getNotes() async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query('notes');
    return maps.map((map) => Note.fromMap(map)).toList();
  }

  // Обновление заметки
  Future<int> updateNote(Note note) async {
    final db = await database;
    return await db.update(
      'notes',
      note.toMap(),
      where: 'id =?',
      whereArgs: [note.id],
    );
  }

  // Удаление заметки
  Future<int> deleteNote(int id) async {
    final db = await database;
    return await db.delete(
      'notes',
      where: 'id =?',
      whereArgs: [id],
    );
  }
}

Быстрые заметки:

  • Мы использовали шаблон одиночка для единственного экземпляра базы данных.
  • openDatabase() инициализирует и создает таблицу notes при первом запуске.
  • Все методы CRUD являются асинхронными и возвращают стандартные int или List<Note>.

Интеграция с UI — отображение и добавление заметок

Мы создадим минимальное приложение с:

  • Списком ListView для отображения заметок
  • FAB для добавления новых заметок
  • Простым экраном для добавления или редактирования заметки

Файл: lib/main.dart

import 'package:flutter/material.dart';
import 'screens/home_screen.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SQLite Notes',
      theme: ThemeData(primarySwatch: Colors.indigo),
      home: const HomeScreen(),
    );
  }
}

Файл: lib/screens/home_screen.dart

import 'package:flutter/material.dart';
import '../db/database_helper.dart';
import '../models/note.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  List<Note> notes = [];

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

  Future<void> _loadNotes() async {
    final data = await DatabaseHelper().getNotes();
    setState(() {
      notes = data;
    });
  }

  void _addNoteDialog() {
    final titleController = TextEditingController();
    final contentController = TextEditingController();

    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: const Text('Добавить заметку'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(controller: titleController, decoration: const InputDecoration(hintText: 'Заголовок')),
            TextField(controller: contentController, decoration: const InputDecoration(hintText: 'Содержание')),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () async {
              final note = Note(title: titleController.text, content: contentController.text);
              await DatabaseHelper().insertNote(note);
              Navigator.of(context).pop();
              _loadNotes();
            },
            child: const Text('Сохранить'),
          ),
        ],
      ),
    );
  }

  Future<void> _deleteNote(int id) async {
    await DatabaseHelper().deleteNote(id);
    _loadNotes();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Мои заметки')),
      body: ListView.builder(
        itemCount: notes.length,
        itemBuilder: (_, index) {
          final note = notes[index];
          return ListTile(
            title: Text(note.title),
            subtitle: Text(note.content),
            trailing: IconButton(
              icon: const Icon(Icons.delete, color: Colors.red),
              onPressed: () => _deleteNote(note.id!),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addNoteDialog,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Бонусные советы и подводные камни

Вот несколько практических советов из моего опыта работы с SQLite в Flutter:

  • Используйте async/await правильно
    Всегда ожидайте вызовы базы данных, чтобы избежать неожиданного поведения или гонок.
  • База данных сохраняется
    Как только база данных создана, она остается до тех пор, пока вы не удалите приложение или не удалите ее вручную. Полезно для тестирования!
  • Обработка ошибок имеет значение
    Оберните операции с базой данных в блоки try-catch, особенно для функций обновления и удаления.
  • Используйте TextEditingControllers мудро
    Не забудьте их утилизировать, если вы используете несколько экранов ввода.

Заключение

Если вы нашли ее полезной или имеете какие-либо отзывы, не стесняйтесь оставить комментарий или связаться. Я бы с удовольствием услышал ваши мысли!

Report Page