iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
Mobile Development

Flutter基礎入門系列 第 16

【Day 16】為資料建立widget

  • 分享至 

  • xImage
  •  

今日筆者將來嘗試將【Day 14】資料庫中的資料以ListView,也就是滾動式的列表顯示在我們的使用者介面之中,並增加一個懸浮按鈕,以建立新的Todo並存入資料庫中。


懸浮按鈕 FloatingActionButton

懸浮按鈕的官方說明文件在此,而Scaffold中有著一項property: floatingActionButton,正是用於將懸浮按鈕加進我們的頁面之中。我們今天的目的之一便是建立一個懸浮按鈕,並且在點擊時能做出最基礎的Block物件。
懸浮按鈕將看起來像這樣。

https://ithelp.ithome.com.tw/upload/images/20240930/20169446WTo6BMlO1J.png p.s.在下面

來看程式吧!

製作懸浮按鈕

之前的文章中,筆者的Todo頁面程式碼僅顯示了Text,而今天我們將更改此部份的內容。

以下是之前的程式碼:

// presentation/pages/todos.dart
// previous code: no floatingActionButton
class TodoPage extends StatelessWidget {
  final TextStyle optionStyle;
  const TodoPage(this.optionStyle, {super.key});
    
  @override
  Widget build(context) => Text('Todo', style: optionStyle);
}

下方則是增加了懸浮按鈕後的程式碼:

import 'package:flutter/material.dart';

class TodoPage extends StatelessWidget {
  final TextStyle optionStyle;
  TodoPage(this.optionStyle, {super.key});
  
  int num = 0;

  @override
  Widget build(context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        // add new timeblock
        onPressed: () {
          db.insert(TimeBlock('name_$num'));
          print('$num\n');
          num++;
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

在以上程式中,筆者為了debug時的方便,將點擊懸浮按鈕後產生的block命名為name_[number],且新的block被儲存時,console會同時顯示出剛新增的編號。

Blocks 行程方塊

在開始動工製作頁面前,先來將我們的class製作個大概的結構。不過首先,筆者想先談一下今日對於過去的程式碼所作的更動。

抽象的方塊:abstract class

經過一番思考,筆者決定將原本於data/models/blocks.dart中的class更改為抽象類別,並利用OOP的概念,將另外製作兩個屬於BlocksDb的子類別: TimeBlocksDbScheduleBlocksDb,並將TimeBlocks與ScheduleBlocks,也就是Todo與Schedule所新增的block分為兩個不同的class。

以下為data/models/blocks.dart更改後的程式碼(部份相同的函式將省略內容):

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

abstract class Block {
  final String name;
  String? category = null, notes = null;
  int? id = null;

  Block(this.name);
  Map<String, Object?> toMap();
}

abstract class BlocksDb {
  String dbFilename, tableName, executeSQL;
  bool dbIsOpen = false;
  late final db;

  BlocksDb(
      {required this.dbFilename,
      required this.tableName,
      required this.executeSQL});

  Future open() async {
    db = openDatabase(
      join(await getDatabasesPath(), dbFilename),
      version: 1,
      onCreate: (db, version) async {
        return await db.execute('''CREATE TABLE $tableName ($executeSQL)''');
      },
    );
    dbIsOpen = true;
  }

  Future<Block> insert(Block block) async {...}
  Future<int> delete(int id) async {...}
  Future<int> update(Block aBlock) async {...}
  Future close() async {...}
  
  Future<List<Map>> getAll() async {
    if (!dbIsOpen) open();
    return await db.rawQuery('SELECT * FROM $tableName');
  }
  
  Future<int> getCount() async =>
      await db.execute('SELECT COUNT(*) from $tableName');
}

程式碼中,Block類別所有的properties將會是TimeBlockScheduleBlock共同擁有的,而其餘這兩個類別的properties將不相同。也因為如此,在此將toMap()改為抽象函式,交給子類別來定義。同理也將BlocksDb在建構新的資料庫Table的SQL語言的程式碼更改為一項properties,可根據要建立的內容改動。

除此之外,筆者也增加了BlocksDb.getAll()BlocksDb.getCount()這兩個函式,分別用於取得資料庫中的表格資料以及列數。

Block的子類別:TimeBlock

data/models/child_blocks.dart是用於放BlockBlocksDb的子類別,其中將包含了TimeBlock, TimeBlocksDb, ScheduleBlock, ScheduleBlocksDb這四個子類別。今日將先製作TimeBlockTimeBlocksDb的大致結構。

至於時間與日期,阿......之後有空再弄好啦!

// data/models/child_blocks.dart
import 'package:schedrag/data/models/blocks.dart';

class TimeBlock extends Block {
  /* TODO: add time variables
   * 1. estimated/spend time
   * 2. deadline
   * */

  TimeBlock(super.name);

  @override
  Map<String, Object?> toMap() {
    return {
      'id': id,
      'name': name,
      'category': category,
      //'estimatedTime': estimatedTime,
      //'deadline': deadline,
      'notes': notes,
    };
  }
}

class TimeBlocksDb extends BlocksDb {
  static const String _executeSQL = '''
    id INTEGER PRIMARY KEY,
    name TEXT, category TEXT,
    estimatedTime DATETIME,
    deadline DATETIME,
    notes TEXT''';

  TimeBlocksDb()
      : super(
            dbFilename: 'timeBlock.db',
            tableName: 'Todos',
            executeSQL: _executeSQL);
}

可以由程式碼中看到,此檔案中含有原本在Blocks的內容:toMap()以及SQL程式碼。而由此方式,能夠更快速後續製作TimeBlocks與ScheduleBlocks,也更方便資料庫的管理。

讓TimeBlocks的資訊於應用程式介面上顯示(尚未完成)

就直接說吧,筆者最初以為只需要於BlocksDb這個類別之中做一個能夠回傳資料庫資料的函數,並用for迴圈將他們一一做成ListTile顯示於應用程式介面上變能夠完工,結果發現的一個問題:The type 'Future\<List\<Map\<dynamic, dynamic>>>' used in the 'for' loop must implement 'Iterable'.
Block中,我們開啟或是處理資料庫中的內容都需要用到async/await,而這類型的函式回傳的物件將會是Future,而Future來自dart:async,並非dart:core的內容,因此無法使用迴圈iterate過其中的資料。故此部份程式碼無法編譯與執行。

以下為筆者原本為此部份寫的內容,因暫時還不知道該如何處理這個問題,因此這部份今日將無法完成,並推遲到後續的日子。

import 'package:flutter/material.dart';
import 'package:schedrag/data/models/child_blocks.dart';

class TodoPage extends StatelessWidget {
  final TextStyle optionStyle;
  final db = TimeBlocksDb();
  TodoPage(this.optionStyle, {super.key});

  int num = 0;

  @override
  //Widget build(BuildContext context) => Text('Todo', style: optionStyle);
  Widget build(context) {
    return Scaffold(
      body: ListView(
        children: [
          for (var row in db.getAll())
            ListTile(title: Text(row['name']), onTap: () {}),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        // add new timeblock
        onPressed: () {
          db.insert(TimeBlock('name_$num'));
          print('name_$num/n');
          num++;
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

這篇文章就到這裡結束,謝謝閱讀到這裡的讀者~
有任何疑問或是想說的都歡迎留言或email!

最後是筆者的小內心戲:
阿阿阿阿阿阿阿阿阿阿阿阿阿怎麼跑不動!!!!跟原本想像的不同,突然遇到這個問題有點挫折。゚ヽ(゚´Д)ノ゚。 希望能夠找到解決方法( ´•̥̥̥ω•̥̥̥ )


上一篇
【Day 15】來講點不一樣的:SQL Server
下一篇
【Day 17】嘗試解決問題:Future 與 non-Future類別間的存取
系列文
Flutter基礎入門30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言