You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter+Supabase实现房源收藏/取消收藏功能技术求助

实现Supabase收藏/取消收藏房源功能的解决方案

1. 确保Supabase初始化完成

先确认你的Flutter项目已完成Supabase初始化,示例代码如下:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Supabase.initialize(
    url: '你的Supabase项目URL',
    anonKey: '你的Supabase匿名密钥',
  );
  runApp(const MyApp());
}

// 全局Supabase客户端实例
final supabase = Supabase.instance.client;

2. 封装Supabase操作工具类

创建SupabaseSavedService类,封装对saved表的查询、插入、删除操作:

import 'package:supabase_flutter/supabase_flutter.dart';

class SupabaseSavedService {
  final SupabaseClient _client = Supabase.instance.client;

  // 检查当前用户是否已收藏指定房源
  Future<bool> checkIfSaved(String userId, int propertyId) async {
    final response = await _client
        .from('saved')
        .select()
        .eq('userid', userId)
        .eq('propertyid', propertyId)
        .maybeSingle(); // 无匹配记录时返回null,不抛出异常

    return response != null;
  }

  // 收藏房源:向saved表插入记录
  Future<void> saveProperty(String userId, int propertyId) async {
    try {
      await _client.from('saved').insert({
        'userid': userId,
        'propertyid': propertyId,
        // created_at由Supabase自动生成,无需手动传入
      });
    } on PostgrestException catch (e) {
      // 处理重复收藏的唯一约束冲突
      if (e.code == '23505') return;
      rethrow;
    }
  }

  // 取消收藏:删除saved表中的对应记录
  Future<void> unsaveProperty(String userId, int propertyId) async {
    await _client
        .from('saved')
        .delete()
        .eq('userid', userId)
        .eq('propertyid', propertyId);
  }
}

重要:在Supabase控制台给saved表添加userid+propertyid联合唯一约束,避免同一用户重复收藏同一房源。

3. 在房源列表项中集成收藏按钮

结合你已实现的CustomLikeButton,在房源列表项中完成完整功能逻辑:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'like_button.dart';
import 'supabase_saved_service.dart';

class PropertyItem extends StatefulWidget {
  final Map<String, dynamic> property;

  const PropertyItem({super.key, required this.property});

  @override
  State<PropertyItem> createState() => _PropertyItemState();
}

class _PropertyItemState extends State<PropertyItem> {
  final _savedService = SupabaseSavedService();
  bool _isLiked = false;
  bool _isLoading = false;
  late final String _currentUserId;

  @override
  void initState() {
    super.initState();
    // 获取当前登录用户ID(需确保用户已登录)
    _currentUserId = supabase.auth.currentUser!.id;
    // 初始化时检查房源收藏状态
    _checkSavedStatus();
  }

  Future<void> _checkSavedStatus() async {
    final propertyId = widget.property['id'] as int;
    _isLiked = await _savedService.checkIfSaved(_currentUserId, propertyId);
    if (mounted) setState(() {});
  }

  Future<void> _toggleLike() async {
    if (_isLoading) return;
    setState(() => _isLoading = true);

    final propertyId = widget.property['id'] as int;
    try {
      if (_isLiked) {
        // 取消收藏
        await _savedService.unsaveProperty(_currentUserId, propertyId);
        setState(() => _isLiked = false);
      } else {
        // 添加收藏
        await _savedService.saveProperty(_currentUserId, propertyId);
        setState(() => _isLiked = true);
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('操作失败: ${e.toString()}')),
        );
      }
    } finally {
      if (mounted) setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      margin: const EdgeInsets.symmetric(vertical: 8),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade200),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(widget.property['address'] ?? ''),
                const SizedBox(height: 8),
                Text('价格: ${widget.property['price']}'),
                const SizedBox(height: 8),
                Text(widget.property['description'] ?? ''),
              ],
            ),
          ),
          // 使用你自定义的收藏按钮
          CustomLikeButton(
            isLiked: _isLiked,
            onTap: _toggleLike,
          ),
          // 加载状态指示器
          if (_isLoading)
            const Padding(
              padding: EdgeInsets.only(left: 8),
              child: SizedBox(
                width: 16,
                height: 16,
                child: CircularProgressIndicator(strokeWidth: 2),
              ),
            ),
        ],
      ),
    );
  }
}

4. 关键配置说明

  • 用户登录校验:在页面初始化时检查用户登录状态,未登录则引导登录,避免currentUser为null的异常。
  • RLS行级安全策略:在Supabase控制台给saved表配置以下策略:
    • SELECT:允许用户查看自己的收藏记录(auth.id() = userid)
    • INSERT:允许用户插入自己的收藏记录(auth.id() = userid)
    • DELETE:允许用户删除自己的收藏记录(auth.id() = userid)

内容的提问来源于stack exchange,提问作者Chanda Musonda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 20:37:09