iT邦幫忙

2025 iThome 鐵人賽

DAY 12
1

昨天實作了「收藏」按鈕,每一次心動的點擊,都代表一則寶貴的靈感被我們珍藏起來~但問題來了:這些收藏的靈感去哪了?它們此刻正靜靜地躺在我們昨天建立的FavoriteProvider狀態裡,等著被看見。

一個只能存、不能看的收藏夾是沒有靈魂的,因此,今天我們的核心任務就是打造一個專屬的「收藏頁面」。這將會是使用者的靈感寶庫,一個可以隨時回來翻閱、回味這些知識靈感的地方。

這個過程中,將學習如何在Flutter中進行頁面跳轉 (Navigation),並親眼見證Riverpod跨頁面同步狀態的驚人威力。

第一步:建立收藏頁面UI骨架

在lib/presentation/目錄下,建立一個新檔案favorites_page.dart。

這個頁面需要讀取favoriteProvider的狀態,所以我們會將它建立為一個ConsumerWidget。它的基本結構包含一個頂部的AppBar和一個用於顯示列表的body。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 引入我們昨天建立的 favorite provider
import 'package:three_inspiration/features/favorite/favorite_provider.dart';

class FavoritesPage extends ConsumerWidget {
  const FavoritesPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 監聽收藏列表的狀態
    final favoritedInsights = ref.watch(favoriteProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('我的收藏'),
      ),
      body: favoritedInsights.isEmpty
          // 如果列表為空,顯示提示訊息
          ? const Center(
              child: Text(
                '你還沒有收藏任何靈感喔!',
                style: TextStyle(fontSize: 18, color: Colors.grey),
              ),
            )
          // 否則,使用 ListView.builder 顯示列表
          : ListView.builder(
              itemCount: favoritedInsights.length,
              itemBuilder: (context, index) {
                final insight = favoritedInsights[index];
                // 這裡我們先用一個簡單的 ListTile
                return Card(
                  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                  child: ListTile(
                    title: Text(insight.summary),
                    subtitle: Wrap(
                      spacing: 8.0,
                      children: insight.tags.map((tag) => Chip(label: Text(tag))).toList(),
                    ),
                  ),
                );
              },
            ),
    );
  }
}

程式碼解析:

  • extends ConsumerWidget:讓這個頁面可以讀取Provider。
  • ref.watch(favoriteProvider):和主頁一樣,用ref.watch來監聽收藏列表,當favoriteProvider的狀態改變時(例如在主頁新增了收藏),這個頁面也會自動刷新。
  • favoritedInsights.isEmpty ? ...: 處理空狀態,提供更好的使用者體驗。
  • ListView.builder:這是顯示長列表最高效能的方式,它只會建立當前螢幕上可見的列表項,非常節省資源。

第二步:實作頁面跳轉

頁面建好了,但要怎麼過去呢?首先,需要在主頁(HomePage)上加一個入口,最適合的位置就是AppBar的右上角。

回到lib/presentation/home_page.dart,修改_HomePageState的build方法,在Scaffold的appBar 參數中加入actions。

// 在 home_page.dart 檔案頂部引入 favorites_page.dart
import 'package:three_inspiration/presentation/favorites_page.dart';

// ... 在 _HomePageState 的 build 方法中 ...
@override
Widget build(BuildContext context) {
  // ...
  return Scaffold(
    appBar: AppBar(
      title: const Text('今日三則靈感'),
      // 在 AppBar 右側加入一個 actions 列表
      actions: [
        IconButton(
          icon: const Icon(Icons.collections_bookmark_outlined),
          onPressed: () {
            // 點擊後,導航到收藏頁面
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const FavoritesPage()),
            );
          },
        ),
      ],
    ),
    body: // ...
  );
}

Navigator.push是Flutter內建的導航方法,它會將一個新的頁面(我們用MaterialPageRoute包裝的 FavoritesPage)推入導航堆疊中,Flutter會自動處理轉場動畫和在AppBar左側顯示返回按鈕。

現在執行App,點擊主頁右上角的書籤圖示,應該就能順利跳轉到我們剛建立的收藏頁面了!

第三步:優化收藏卡片與互動

一個只能看的列表是不夠的,還需要能互動,現在來實作「取消收藏」的功能。
回到favorites_page.dart,將ListTile變得更豐富一些,加入一個刪除按鈕。

// ... 在 ListView.builder 的 itemBuilder 中 ...
return Card(
  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  child: ListTile(
    title: Text(insight.summary),
    subtitle: Padding(
      padding: const EdgeInsets.only(top: 8.0),
      // 根據 Wireframe,為未來的筆記功能預留空間
      child: Text(
        '點此新增筆記...',
        style: TextStyle(color: Colors.grey.shade600, fontStyle: FontStyle.italic),
      ),
    ),
    // 在列表項的尾部加入一個取消收藏的按鈕
    trailing: IconButton(
      icon: const Icon(Icons.delete_outline, color: Colors.redAccent),
      onPressed: () {
        // 呼叫 provider 的 removeFavorite 方法
        ref.read(favoriteProvider.notifier).removeFavorite(insight.id);
      },
    ),
  ),
);

我們使用了ref.read(favoriteProvider.notifier).removeFavorite(insight.id)。當你點擊刪除按鈕時:

  1. read 會觸發FavoriteNotifier中的removeFavorite方法。
  2. FavoriteNotifier的狀態(收藏列表)被更新了(移除了該項)。
  3. 由於FavoritesPage正在用ref.watch監聽這個Provider,它會立刻收到通知。
  4. build方法被觸發重建,ListView.builder根據新的、縮短了的列表重新渲染,被刪除的卡片就立刻從畫面上消失了!

明日預告:使用Hive本地小倉庫

日更靈感App越來越像一個完整的產品了,但它目前還是有健忘症。只要你關閉App再重新打開,所有收藏的靈感都會消失得無影無蹤。

為了解決這個問題,明天將引入本地端資料Hive,學習如何將我們的收藏資料持久化地儲存到手機上,讓它們在 App重啟後依然存在。敬請期待!


【哈囉你好:)感謝你的閱讀!其他我會常出沒的地方:Threads


上一篇
30 天做一個極簡App:互動按鈕「一鍵保留、捨棄、收藏」
系列文
Mobile Dev|日更靈感來源 App:Flutter × LLM × n8n,每天只推 3 則!12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言