iT邦幫忙

2024 iThome 鐵人賽

DAY 22
0
Mobile Development

Flutter基礎入門系列 第 22

【Day 22】ChangeNotifier與Consumer的實作應用

  • 分享至 

  • xImage
  •  

接續前面兩篇的內容,今天筆者將來實作ChangeNotifier,並讓應用程式在增加新的Todo物件時,畫面會跟著一起更新,顯示新加入的內容。因為這部份的概念於前兩篇中已經談過了,因此本篇將不會有太詳細的說明。


ChangeNotifier

因在此想追蹤儲存Todo的資料庫是否有內容變動,因此我們的data/models/blocks.dart中的BlocksDb須設定為ChangeNotifier的子類別(child class)。至於child_blocks.dart檔案中的內容本次則不需要修改,因其中的類型都是blocks.dart中的子類別。

更改後的程式碼如下:

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

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

  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();
    return directory.path;
  }

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

  Future<Block> insert(Block block) async {
    if (!dbIsOpen) open();

    block.id = await db.insert(tableName, block.toMap());
    notifyListeners();
    return block;
  }

  Future<int> delete(int id) async {
    if (!dbIsOpen) open();

    var idx = await db.delete(tableName, where: 'id = ?', whereArgs: [id]);
    notifyListeners();
    return idx;
  }

  Future<int> update(Block aBlock) async {
    if (!dbIsOpen) open();

    var idx = await db.update(tableName, aBlock.toMap(),
        where: 'id = ?', whereArgs: [aBlock.id]);
    notifyListeners();
    return idx;
  }

  Future close() async {
    dbIsOpen = false;
    return await db.close();
  }

  Future<int> getCount() async =>
      await db.execute('SELECT COUNT(*) from $tableName');
}

這部份的變動十分簡單,只需要改以下部份:

  1. class BlocksDb extends ChangeNotifier => 改為子類別
  2. 會對資料庫做更動的函式都加上notifyListeners() (open(), insert(), delete(), update())

ChangeNotifierProvider與Consumer

很可能會有人疑惑,ChangeNotifierProvider該加在哪裡會比較適當呢?那便是程式越末端,離Consumer越近的上方位置越好,如此便可以避免太多物件因ChangeNotifier的通知而受到影響。至於Consumer,當然則是讓它盡可能只包含需要更新的widget,也就是todo頁面中的清單。

以下為更改後的程式碼:

class _TodoPageState extends State<TodoPage> {
  late Future<List<TimeBlock>?> dbList;
  var num = 0;

  @override
  Widget build(context) {
    return ChangeNotifierProvider(
      create: (context) => TimeBlocksDb(),
      child: Consumer<TimeBlocksDb>(builder: (context, db, child) {
        dbList = db.getAll();
        return Scaffold(
          body: FutureBuilder<List<TimeBlock>?>(
            builder: (con, snap) {
              if (snap.hasData) {
                return ListView.builder(
                  itemCount: snap.data!.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                        title: Text(snap.data![index].name.toString()),
                        onTap: () {});
                  },
                );
              } else {
                return const Center(child: Text("Waiting..."));
              }
            },
            future: dbList,
          ),
          floatingActionButton: FloatingActionButton(
            // add new timeblock
            onPressed: () {
              db.insert(TimeBlock.name('name_$num'));
              print('name_$num\n');
              num++;
            },
            child: const Icon(Icons.add),
          ),
        );
      }),
    );
  }
}

由上方可以看到,原本Todo頁面僅有的widget: Scaffold現在被包在Consumer之內,而Consumer又被包在ChangeNotifierProvider內部。並且,程式碼中所有的TimeBlocksDb物件皆改為使用Consumer的建構式中所設的db,而非_TodoPageState的property: TimeBlocksDb db = TimeBlocksDb();


謝謝閱讀到這裡的讀者。有任何問題或想說的都歡迎留言或email,明天將會繼續努力更新的!
email: nnyjan02426@gmail.com
github: nnyjan02426
Schedrag: nnyjan02426/Schedrag


上一篇
【Day 21】獲得狀態變更通知並更新UI吧!
下一篇
【Day 23】增加功能:利用FormField新增資料
系列文
Flutter基礎入門30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言