SQLite Blog, Flutter: Second Part

We’ve already started building the SQLite Blog application in Flutter. The previous section has discussed the application structure. In this second part, we’ll concentrate on database connection, data model classes.

As we proceed, we’ll also learn how we can develop the same application thematically. Therefore, we’ll add more functionalities. We’ll keep in mind that our application should look great and should be user friendly.

First thing first. Let’s recapitulate a few things about SQLite database and Flutter.

Firstly, we’ll use a Flutter package or plugin, sqflite which is available in pub.dev.

Secondly, we also need to use Future API, async, await keywords, and then functions to make it successful.

We’ve discussed Future, await and async for absolute beginners in previous section, is Flutter single thread? Therefore, if you’re a beginner, you might take a look before we start.

Which DB is best for Flutter?

SQLite database is one of the best DB for Flutter. Although there are couple more. Like Hive, Firebase, etc.

However we’re using SQLite database for this Blog application in Flutter, because it’s local, fast and easy to maintain.

First of all, we need a Blog data model.

Let’s keep that class in model folder.

const String tableOfBlogs = 'Blogs';

class BlogFields {
  static final List<String> values = [
    /// Adding all fields
    id, title, description, time
  ];

  static const String id = '_id';
  static const String title = 'title';
  static const String description = 'description';
  static const String time = 'time';
}

class Blog {
  final int? id;
  final String title;
  final String description;
  final DateTime createdTime;

  const Blog({
    this.id,
    required this.title,
    required this.description,
    required this.createdTime,
  });

  Blog copy({
    int? id,
    String? title,
    String? description,
    DateTime? createdTime,
  }) =>
      Blog(
        id: id ?? this.id,
        title: title ?? this.title,
        description: description ?? this.description,
        createdTime: createdTime ?? this.createdTime,
      );

  static Blog fromJson(Map<String, Object?> json) => Blog(
        id: json[BlogFields.id] as int?,
        title: json[BlogFields.title] as String,
        description: json[BlogFields.description] as String,
        createdTime: DateTime.parse(json[BlogFields.time] as String),
      );

  Map<String, Object?> toJson() => {
        BlogFields.id: id,
        BlogFields.title: title,
        BlogFields.description: description,
        BlogFields.time: createdTime.toIso8601String(),
      };
}

We need to Map the data model object. It’s because Flutter wants a list to display.

Retrieving blog items on Home page
Retrieving blog items on Home page

Second thing we need a database handler class that will create the table, and at the same time, it’ll do the CRUD.

We’ve learned what CRUD is. Create, retrieve, update and delete.

Adding Blog items to SQLite database in Flutter
Adding Blog items to SQLite database in Flutter

In this SQLite Blog application we’ll also do the same. However, we’ve tweaked the previous home page code to accommodate a floating action button.

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import '../model/blog.dart';

class BlogDatabaseHandler {
  static final BlogDatabaseHandler instance = BlogDatabaseHandler._init();

  static Database? _database;

  BlogDatabaseHandler._init();

  Future<Database> get database async {
    if (_database != null) return _database!;

    _database = await _initDB('newblogs.db');
    return _database!;
  }

  Future<Database> _initDB(String filePath) async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, filePath);

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

  Future _createDB(Database db, int version) async {
    const idType = 'INTEGER PRIMARY KEY AUTOINCREMENT';
    const textType = 'TEXT NOT NULL';

    await db.execute('''
CREATE TABLE $tableOfBlogs ( 
  ${BlogFields.id} $idType, 
  ${BlogFields.title} $textType,
  ${BlogFields.description} $textType,
  ${BlogFields.time} $textType
  )
''');
  }

  Future<Blog> create(Blog blog) async {
    final db = await instance.database;

    final id = await db.insert(tableOfBlogs, blog.toJson());
    return blog.copy(id: id);
  }

  Future<Blog> readBlog(int id) async {
    final db = await instance.database;

    final maps = await db.query(
      tableOfBlogs,
      columns: BlogFields.values,
      where: '${BlogFields.id} = ?',
      whereArgs: [id],
    );

    if (maps.isNotEmpty) {
      return Blog.fromJson(maps.first);
    } else {
      throw Exception('ID $id not found');
    }
  }

  Future<List<Blog>> readAllBlogs() async {
    final db = await instance.database;

    const orderBy = '${BlogFields.time} ASC';

    final result = await db.query(tableOfBlogs, orderBy: orderBy);

    return result.map((json) => Blog.fromJson(json)).toList();
  }

  Future<int> update(Blog blog) async {
    final db = await instance.database;

    return db.update(
      tableOfBlogs,
      blog.toJson(),
      where: '${BlogFields.id} = ?',
      whereArgs: [blog.id],
    );
  }

  Future<int> delete(int id) async {
    final db = await instance.database;

    return await db.delete(
      tableOfBlogs,
      where: '${BlogFields.id} = ?',
      whereArgs: [id],
    );
  }

  Future close() async {
    final db = await instance.database;

    db.close();
  }
}

In the above code, everything is very verbose.

Firstly, we’ve mentioned a path so the database gets created locally and stores data locally.

Secondly, we’ve created the database and table with fields. As we see, we’ve kept it simple.

Thirdly, we’ve created methods that will read, update, delete items.

Finally, we’ve closed the database.

At the same time, we’ve changed the code of our home page slightly. As a result, we can now add or update the blog items either from AppBar, or through the floating action button.

import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import '/model/blogs.dart';
import '/model/blog.dart';
import 'edit.dart';
import 'detail.dart';
import '/controller/blog_card.dart';

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

  @override
  _AllPagesState createState() => _AllPagesState();
}

class _AllPagesState extends State<AllPages> {
  late List<Blog> blogs;
  bool isLoading = false;

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

    refreshingAllBogs();
  }

  @override
  void dispose() {
    BlogDatabaseHandler.instance.close();

    super.dispose();
  }

  Future refreshingAllBogs() async {
    setState(() => isLoading = true);

    blogs = await BlogDatabaseHandler.instance.readAllBlogs();

    setState(() => isLoading = false);
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: const Text(
            'Blogs',
            style: TextStyle(fontSize: 24),
          ),
          actions: [
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
              child: ElevatedButton(
                style: ElevatedButton.styleFrom(
                  onPrimary: Colors.white,
                  primary: Colors.pink.shade900,
                ),
                onPressed: () async {
                  await Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) => const EditPage()),
                  );

                  refreshingAllBogs();
                },
                child: const Text(
                  'Add or Update Blog',
                  style: TextStyle(
                    fontSize: 20,
                  ),
                ),
              ),
            )
          ],
        ),
        body: Center(
          child: isLoading
              ? const CircularProgressIndicator()
              : blogs.isEmpty
                  ? const Text(
                      'No Blogs in the beginning...',
                      style: TextStyle(color: Colors.white, fontSize: 60),
                    )
                  : buildingAllBlogs(),
        ),
        floatingActionButton: FloatingActionButton.extended(
          tooltip: 'Add or Update Blog',
          foregroundColor: Colors.white,
          backgroundColor: Colors.pink.shade900,
          onPressed: () async {
            await Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => const EditPage()),
            );

            refreshingAllBogs();
          },
          label: const Text(
            'Add or Update Blog',
            style: TextStyle(
              fontSize: 30,
            ),
          ),
        ),
      );

  Widget buildingAllBlogs() => StaggeredGridView.countBuilder(
        padding: const EdgeInsets.all(8),
        itemCount: blogs.length,
        staggeredTileBuilder: (index) => const StaggeredTile.fit(2),
        crossAxisCount: 4,
        mainAxisSpacing: 4,
        crossAxisSpacing: 4,
        itemBuilder: (context, index) {
          final blog = blogs[index];

          return GestureDetector(
            onTap: () async {
              await Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => DetailPage(blogId: blog.id!),
              ));

              refreshingAllBogs();
            },
            child: BlogCard(blog: blog, index: index),
          );
        },
      );
}

As a result, when there is no blog items, the home page looks like the following one.

Adding a Floating action button to SQLite Blog Home page in Flutter
Adding a Floating action button to SQLite Blog Home page in Flutter

In the next part, or section we’ll take a look at the edit page, where actual action takes place.

After that, we’ll also see how we can use the Card widget to display all inserted data.

As we’re changing the previous code, we keep them in separate GitHub branch. For full code for this section, please visit the respective GitHub repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

2 responses to “SQLite Blog, Flutter: Second Part”

  1. […] already built a significant part of a SQLite Blog Application in Flutter. We might also see the progress of the initial phase in this section – SQLite Blog […]

  2. […] only that, before doing that, in a step by step process we’ve built the SQLite Blog Application in Flutter. We might also see the progress of the initial phase in this section – SQLite Blog […]

Leave a Reply