Flutter中处理Supabase查询结果的方案(含登录校验场景)
Flutter自定义Supabase查询校验实现(受访者登录)
完整代码示例
import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; void main() async { // 初始化Supabase(替换为你的项目URL和匿名密钥) await Supabase.initialize( url: '你的Supabase项目URL', anonKey: '你的Supabase匿名密钥', ); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '受访者登录', home: LoginPage(), ); } } // 受访者模型类,映射数据库表字段 class Respondent { final int id; final String householdNumber; final String pinPassword; // 根据你的表结构添加其他字段(如姓名、联系方式等) Respondent({ required this.id, required this.householdNumber, required this.pinPassword, // 其他字段初始化 }); // 将Supabase返回的Map数据转换为Respondent对象 factory Respondent.fromMap(Map<String, dynamic> map) { return Respondent( id: map['id'] as int, householdNumber: map['household_number'] as String, pinPassword: map['pin_password'] as String, // 其他字段映射 ); } } class LoginPage extends StatefulWidget { @override State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { final _householdNumberController = TextEditingController(); final _pinPasswordController = TextEditingController(); final _supabase = Supabase.instance.client; bool _isLoading = false; Future<void> _handleLogin() async { setState(() => _isLoading = true); final householdNumber = _householdNumberController.text.trim(); final pinPassword = _pinPasswordController.text.trim(); // 输入合法性校验 if (householdNumber.isEmpty || pinPassword.isEmpty) { _showErrorDialog('请填写完整的家庭编号和PIN码'); setState(() => _isLoading = false); return; } try { // 查询匹配的受访者,maybeSingle()自动处理结果数量 final response = await _supabase .from('respondents') .select() .eq('household_number', householdNumber) .eq('pin_password', pinPassword) .maybeSingle(); if (response != null) { // 将查询结果转换为强类型对象 final respondent = Respondent.fromMap(response); // 跳转至问卷页面并传递受访者信息 Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => SurveyForm(respondent: respondent), ), ); } else { _showErrorDialog('家庭编号或PIN码不正确'); } } on PostgrestException catch (e) { // 捕获多条匹配结果的异常(错误码PGRST116) if (e.code == 'PGRST116') { _showErrorDialog('发现多个匹配的受访者,请联系管理员'); } else { _showErrorDialog('登录失败:${e.message}'); } } catch (e) { // 处理网络异常、数据库连接失败等通用错误 _showErrorDialog('网络异常,请稍后重试'); } finally { setState(() => _isLoading = false); } } // 错误提示弹窗 void _showErrorDialog(String message) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('登录失败'), content: Text(message), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('确定'), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('受访者登录')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( controller: _householdNumberController, decoration: const InputDecoration( labelText: '家庭编号', border: OutlineInputBorder(), ), enabled: !_isLoading, ), const SizedBox(height: 16), TextField( controller: _pinPasswordController, decoration: const InputDecoration( labelText: 'PIN码', border: OutlineInputBorder(), ), obscureText: true, enabled: !_isLoading, ), const SizedBox(height: 24), ElevatedButton( onPressed: _isLoading ? null : _handleLogin, child: _isLoading ? const CircularProgressIndicator(color: Colors.white) : const Text('登录'), ), ], ), ), ); } } class SurveyForm extends StatelessWidget { final Respondent respondent; const SurveyForm({super.key, required this.respondent}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('调查问卷')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('欢迎,家庭编号:${respondent.householdNumber}'), // 此处添加调查问卷表单组件 ], ), ), ); } }
核心逻辑说明
1. 模型类设计
定义Respondent类实现数据库字段与代码对象的映射,通过fromMap方法将Supabase返回的原始Map数据转换为强类型对象,避免直接操作松散的Map结构,提升代码可读性和可维护性。
2. 查询与结果处理
使用maybeSingle()替代弃用的execute():
- 返回0条结果:返回
null,触发"账号或密码错误"提示; - 返回1条结果:返回该条数据的Map对象,转换为
Respondent后跳转至问卷页面; - 返回多条结果:抛出
PostgrestException(错误码PGRST116),触发"多个匹配结果"提示。
3. 错误处理分层
- 输入校验:提前拦截空输入,避免无效数据库查询;
- 业务异常捕获:处理多条匹配、查询失败等业务相关错误;
- 通用异常捕获:处理网络中断、数据库连接失败等系统级错误;
- 统一弹窗提示:给用户清晰的错误反馈,避免无意义的报错信息。
4. 页面状态管理
添加_isLoading状态控制按钮可用性和加载提示,避免用户重复点击,提升交互体验。
内容的提问来源于stack exchange,提问作者Sheenergizer
相关产品推荐
相关产品推荐

