Flutter for OpenHarmony 商城App实战 - 地址编辑实现

地址编辑是地址管理系统中的核心功能。用户需要能够添加新地址、编辑现有地址、验证地址信息的完整性和正确性。一个完整的地址编辑系统需要支持表单验证、数据持久化、默认地址管理、地址标签分类等功能。本文将详细讲解如何在 Flutter for OpenHarmony 项目中实现一个功能完整的地址编辑页面,包括表单设计、数据验证、状态管理、用户反馈和错误处理等功能。

表单控制器管理

地址编辑页面需要管理多个表单字段的状态和数据。

dart 复制代码
// 地址编辑页面状态管理
class _AddressEditPageState extends State<AddressEditPage> {
  // 表单验证key
  final _formKey = GlobalKey<FormState>();
  
  // 各字段的文本编辑控制器
  final _nameController = TextEditingController();      // 收货人
  final _phoneController = TextEditingController();    // 手机号
  final _provinceController = TextEditingController(); // 省份
  final _cityController = TextEditingController();     // 城市
  final _districtController = TextEditingController(); // 区/县
  final _detailController = TextEditingController();   // 详细地址
  
  // 地址标签和默认状态
  String? _tag;
  bool _isDefault = false;
  
  // 正在编辑的地址对象
  Address? _editingAddress;

  @override
  void dispose() {
    // 释放所有控制器资源
    _nameController.dispose();
    _phoneController.dispose();
    _provinceController.dispose();
    _cityController.dispose();
    _districtController.dispose();
    _detailController.dispose();
    super.dispose();
  }
}

这个表单控制器管理展示了如何组织表单状态:

控制器管理:

  • 为每个表单字段创建独立的 TextEditingController
  • dispose() 中释放所有控制器资源
  • 避免内存泄漏和资源浪费

状态变量:

  • _formKey 用于表单验证
  • _editingAddress 记录正在编辑的地址
  • _tag_isDefault 管理地址属性

资源管理:

  • 及时释放控制器
  • 防止内存泄漏
  • 确保应用性能

地址数据初始化

编辑现有地址时,需要从路由参数中获取地址数据并填充表单。

dart 复制代码
// 地址数据初始化
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  
  // 从路由参数获取要编辑的地址
  final address = ModalRoute.of(context)?.settings.arguments as Address?;
  
  // 只在第一次时初始化,避免重复加载
  if (address != null && _editingAddress == null) {
    _editingAddress = address;
    
    // 将地址数据填充到各个表单字段
    _nameController.text = address.name;
    _phoneController.text = address.phone;
    _provinceController.text = address.province;
    _cityController.text = address.city;
    _districtController.text = address.district;
    _detailController.text = address.detail;
    
    // 恢复地址标签和默认状态
    _tag = address.tag;
    _isDefault = address.isDefault;
  }
}

这个初始化逻辑展示了如何加载地址数据:

数据获取:

  • ModalRoutesettings.arguments 获取地址参数
  • 类型转换为 Address 对象
  • 检查是否为编辑模式

表单填充:

  • 将地址各字段值填充到对应的控制器
  • 恢复地址标签和默认状态
  • 使用 _editingAddress == null 检查避免重复初始化

生命周期管理:

  • didChangeDependencies() 中初始化
  • 确保依赖关系正确处理
  • 只初始化一次

表单验证

表单验证确保用户输入的地址数据完整和正确。

dart 复制代码
// 表单字段验证
class AddressFormValidator {
  // 验证收货人名字
  static String? validateName(String? value) {
    if (value == null || value.trim().isEmpty) {
      return '请输入收货人名字';
    }
    if (value.length > 20) {
      return '收货人名字不能超过20个字符';
    }
    return null;
  }

  // 验证手机号码
  static String? validatePhone(String? value) {
    if (value == null || value.trim().isEmpty) {
      return '请输入手机号';
    }
    
    // 移除空格后验证
    final cleanPhone = value.replaceAll(' ', '');
    
    // 检查是否为11位数字
    if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(cleanPhone)) {
      return '请输入有效的11位手机号';
    }
    return null;
  }

  // 验证省份
  static String? validateProvince(String? value) {
    if (value == null || value.trim().isEmpty) {
      return '请输入省份';
    }
    return null;
  }

  // 验证城市
  static String? validateCity(String? value) {
    if (value == null || value.trim().isEmpty) {
      return '请输入城市';
    }
    return null;
  }

  // 验证详细地址
  static String? validateDetail(String? value) {
    if (value == null || value.trim().isEmpty) {
      return '请输入详细地址';
    }
    if (value.length > 100) {
      return '详细地址不能超过100个字符';
    }
    return null;
  }
}

// 在表单字段中使用验证器
TextFormField(
  controller: _nameController,
  decoration: const InputDecoration(
    border: OutlineInputBorder(),
    labelText: '收货人',
    prefixIcon: Icon(Icons.person),
  ),
  validator: AddressFormValidator.validateName,
)

TextFormField(
  controller: _phoneController,
  keyboardType: TextInputType.phone,
  decoration: const InputDecoration(
    border: OutlineInputBorder(),
    labelText: '手机号',
    prefixIcon: Icon(Icons.phone),
  ),
  validator: AddressFormValidator.validatePhone,
)

TextFormField(
  controller: _detailController,
  maxLines: 2,
  decoration: const InputDecoration(
    border: OutlineInputBorder(),
    labelText: '详细地址',
  ),
  validator: AddressFormValidator.validateDetail,
)

这个验证逻辑展示了如何验证表单数据:

验证规则:

  • 收货人:不能为空,不超过20个字符
  • 手机号:必须是11位数字,以1开头,第二位为3-9
  • 省份/城市:不能为空
  • 详细地址:不能为空,不超过100个字符

验证方法:

  • 创建专门的验证器类
  • 每个字段对应一个验证方法
  • TextFormFieldvalidator 参数中使用

用户体验:

  • 实时显示验证错误
  • 清晰的错误提示信息
  • 帮助用户快速修正

地址保存逻辑

保存地址时需要验证表单、创建地址对象、更新应用状态。

dart 复制代码
// 保存地址的核心逻辑
void _saveAddress() {
  // 第一步:验证表单
  if (!_formKey.currentState!.validate()) {
    return;
  }

  // 获取应用状态
  final appState = AppStateScope.of(context);

  // 第二步:创建地址对象
  final address = Address(
    // 如果是编辑模式,使用原地址ID;否则生成新ID
    id: _editingAddress?.id ?? 
        'addr_${DateTime.now().millisecondsSinceEpoch}',
    
    // 从表单控制器获取数据并去除空格
    name: _nameController.text.trim(),
    phone: _phoneController.text.trim(),
    province: _provinceController.text.trim(),
    city: _cityController.text.trim(),
    district: _districtController.text.trim(),
    detail: _detailController.text.trim(),
    
    // 地址标签和默认状态
    tag: _tag,
    isDefault: _isDefault,
  );

  // 第三步:更新应用状态
  if (_editingAddress != null) {
    // 编辑模式:更新现有地址
    appState.updateAddress(address);
  } else {
    // 添加模式:添加新地址
    appState.addAddress(address);
  }

  // 第四步:显示成功提示
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(
        _editingAddress != null ? '地址已更新' : '地址已添加',
      ),
      backgroundColor: Colors.green,
    ),
  );

  // 第五步:返回上一页
  Navigator.of(context).pop();
}

这个保存逻辑展示了如何处理地址保存:

保存流程:

  • 验证表单:确保所有必填字段都有效
  • 创建对象 :构建 Address 对象
  • 更新状态 :调用 AppState 的方法
  • 用户反馈:显示成功提示
  • 页面返回:返回到上一页

数据处理:

  • 使用 trim() 移除空格
  • 生成唯一的地址ID
  • 区分添加和编辑模式

状态管理:

  • 调用 updateAddress()addAddress()
  • 自动处理默认地址逻辑
  • 通知所有监听者更新UI

收货人信息表单

收货人信息是地址的基本部分,包括名字和电话。

dart 复制代码
// 构建收货人信息卡片
Widget _buildReceiverCard() {
  return ShopCard(
    child: Column(
      children: [
        // 收货人名字输入框
        TextFormField(
          controller: _nameController,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            labelText: '收货人',
            prefixIcon: Icon(Icons.person),
            hintText: '请输入收货人名字',
          ),
          validator: (v) => v?.trim().isEmpty == true 
              ? '请输入收货人' 
              : null,
        ),
        const SizedBox(height: 16),
        
        // 手机号输入框
        TextFormField(
          controller: _phoneController,
          keyboardType: TextInputType.phone,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            labelText: '手机号',
            prefixIcon: Icon(Icons.phone),
            hintText: '请输入11位手机号',
          ),
          validator: (v) => v?.trim().isEmpty == true 
              ? '请输入手机号' 
              : null,
        ),
      ],
    ),
  );
}

这个收货人信息表单展示了如何设计基本信息输入:

表单字段:

  • 收货人名字 :使用 Icons.person 图标
  • 手机号 :使用 TextInputType.phone 键盘类型

输入优化:

  • 提供清晰的标签和提示文本
  • 使用合适的键盘类型
  • 添加前缀图标提示

验证反馈:

  • 实时验证用户输入
  • 显示清晰的错误提示
  • 帮助用户快速修正

地址信息表单

地址信息包括省份、城市、区/县和详细地址。

dart 复制代码
// 构建地址信息卡片
Widget _buildAddressCard() {
  return ShopCard(
    child: Column(
      children: [
        // 省份输入框
        TextFormField(
          controller: _provinceController,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            labelText: '省份',
            hintText: '如:上海市',
          ),
          validator: (v) => v?.trim().isEmpty == true 
              ? '请输入省份' 
              : null,
        ),
        const SizedBox(height: 16),
        
        // 城市输入框
        TextFormField(
          controller: _cityController,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            labelText: '城市',
            hintText: '如:浦东新区',
          ),
          validator: (v) => v?.trim().isEmpty == true 
              ? '请输入城市' 
              : null,
        ),
        const SizedBox(height: 16),
        
        // 区/县输入框(可选)
        TextFormField(
          controller: _districtController,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            labelText: '区/县',
            hintText: '如:陆家嘴',
          ),
        ),
        const SizedBox(height: 16),
        
        // 详细地址输入框
        TextFormField(
          controller: _detailController,
          maxLines: 2,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            labelText: '详细地址',
            hintText: '请输入街道、楼号、房间号等',
          ),
          validator: (v) => v?.trim().isEmpty == true 
              ? '请输入详细地址' 
              : null,
        ),
      ],
    ),
  );
}

这个地址信息表单展示了如何组织地址输入:

表单结构:

  • 省份:必填,如"上海市"
  • 城市:必填,如"浦东新区"
  • 区/县:可选,提供更详细的位置
  • 详细地址:必填,支持多行输入

输入特性:

  • 详细地址使用 maxLines: 2 支持多行
  • 提供示例提示帮助用户理解
  • 清晰的字段标签

验证规则:

  • 省份、城市、详细地址为必填
  • 区/县为可选字段
  • 实时验证和错误提示

地址标签和默认设置

地址标签用于分类,默认地址在结算时自动使用。

dart 复制代码
// 构建标签和默认设置卡片
Widget _buildTagAndDefaultCard() {
  return ShopCard(
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 地址标签选择
        Text(
          '标签',
          style: Theme.of(context).textTheme.titleMedium,
        ),
        const SizedBox(height: 12),
        
        // 标签选择芯片
        Wrap(
          spacing: 8,
          children: ['家', '公司', '学校'].map((tag) {
            return ChoiceChip(
              label: Text(tag),
              selected: _tag == tag,
              onSelected: (selected) {
                setState(() {
                  _tag = selected ? tag : null;
                });
              },
            );
          }).toList(),
        ),
        const SizedBox(height: 12),
        
        // 默认地址开关
        SwitchListTile(
          title: const Text('设为默认地址'),
          subtitle: const Text('在结算时自动使用此地址'),
          value: _isDefault,
          onChanged: (v) {
            setState(() => _isDefault = v);
          },
          contentPadding: EdgeInsets.zero,
        ),
      ],
    ),
  );
}

这个标签和默认设置展示了如何管理地址属性:

标签选择:

  • 使用 ChoiceChip 组件
  • 支持"家"、"公司"、"学校"等标签
  • 单选模式,只能选择一个标签

默认地址:

  • 使用 SwitchListTile 开关
  • 提供清晰的说明文本
  • 在结算时自动使用

用户交互:

  • 点击芯片切换标签
  • 切换开关设置默认地址
  • 实时更新状态

默认地址管理

应用状态需要正确处理默认地址的逻辑。

dart 复制代码
// 添加地址时的默认地址处理
void addAddress(Address address) {
  // 如果新地址设置为默认,取消其他地址的默认标记
  if (address.isDefault) {
    for (int i = 0; i < _addresses.length; i++) {
      if (_addresses[i].isDefault) {
        // 创建新的地址对象,将isDefault设为false
        _addresses[i] = _addresses[i].copyWith(isDefault: false);
      }
    }
  }
  
  // 添加新地址
  _addresses.add(address);
  notifyListeners();
}

// 更新地址时的默认地址处理
void updateAddress(Address address) {
  final index = _addresses.indexWhere((a) => a.id == address.id);
  
  if (index >= 0) {
    // 如果更新的地址设置为默认
    if (address.isDefault) {
      for (int i = 0; i < _addresses.length; i++) {
        // 取消其他地址的默认标记(除了当前地址)
        if (_addresses[i].isDefault && i != index) {
          _addresses[i] = _addresses[i].copyWith(isDefault: false);
        }
      }
    }
    
    // 更新地址
    _addresses[index] = address;
    notifyListeners();
  }
}

// 获取默认地址
Address? get defaultAddress {
  try {
    return _addresses.firstWhere((a) => a.isDefault);
  } catch (e) {
    return null;
  }
}

这个默认地址管理展示了如何处理默认地址逻辑:

添加地址:

  • 检查新地址是否设置为默认
  • 如果是,取消其他地址的默认标记
  • 确保只有一个默认地址

更新地址:

  • 检查更新的地址是否设置为默认
  • 排除当前地址,取消其他地址的默认标记
  • 避免重复处理

查询默认地址:

  • 使用 firstWhere() 查找默认地址
  • 使用 try-catch 处理异常
  • 返回 null 如果没有默认地址

业务规则:

  • 最多只有一个默认地址
  • 自动管理默认地址状态
  • 保证数据一致性

页面完整实现

地址编辑页面的完整实现包括表单、验证和保存。

dart 复制代码
// 完整的地址编辑页面
class AddressEditPage extends StatefulWidget {
  const AddressEditPage({super.key});

  @override
  State<AddressEditPage> createState() => _AddressEditPageState();
}

class _AddressEditPageState extends State<AddressEditPage> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _phoneController = TextEditingController();
  final _provinceController = TextEditingController();
  final _cityController = TextEditingController();
  final _districtController = TextEditingController();
  final _detailController = TextEditingController();
  
  String? _tag;
  bool _isDefault = false;
  Address? _editingAddress;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final address = ModalRoute.of(context)?.settings.arguments as Address?;
    if (address != null && _editingAddress == null) {
      _editingAddress = address;
      _nameController.text = address.name;
      _phoneController.text = address.phone;
      _provinceController.text = address.province;
      _cityController.text = address.city;
      _districtController.text = address.district;
      _detailController.text = address.detail;
      _tag = address.tag;
      _isDefault = address.isDefault;
    }
  }

  @override
  void dispose() {
    _nameController.dispose();
    _phoneController.dispose();
    _provinceController.dispose();
    _cityController.dispose();
    _districtController.dispose();
    _detailController.dispose();
    super.dispose();
  }

  void _save() {
    if (!_formKey.currentState!.validate()) return;
    
    final appState = AppStateScope.of(context);
    final address = Address(
      id: _editingAddress?.id ?? 
          'addr_${DateTime.now().millisecondsSinceEpoch}',
      name: _nameController.text.trim(),
      phone: _phoneController.text.trim(),
      province: _provinceController.text.trim(),
      city: _cityController.text.trim(),
      district: _districtController.text.trim(),
      detail: _detailController.text.trim(),
      tag: _tag,
      isDefault: _isDefault,
    );

    if (_editingAddress != null) {
      appState.updateAddress(address);
    } else {
      appState.addAddress(address);
    }
    
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          _editingAddress != null ? '地址已更新' : '地址已添加',
        ),
      ),
    );
    
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
    return SimpleScaffoldPage(
      title: _editingAddress != null ? '编辑地址' : '添加地址',
      child: Form(
        key: _formKey,
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            // 收货人信息
            ShopCard(
              child: Column(
                children: [
                  TextFormField(
                    controller: _nameController,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: '收货人',
                      prefixIcon: Icon(Icons.person),
                    ),
                    validator: (v) => v?.trim().isEmpty == true 
                        ? '请输入收货人' 
                        : null,
                  ),
                  const SizedBox(height: 16),
                  TextFormField(
                    controller: _phoneController,
                    keyboardType: TextInputType.phone,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: '手机号',
                      prefixIcon: Icon(Icons.phone),
                    ),
                    validator: (v) => v?.trim().isEmpty == true 
                        ? '请输入手机号' 
                        : null,
                  ),
                ],
              ),
            ),
            const SizedBox(height: 16),
            
            // 地址信息
            ShopCard(
              child: Column(
                children: [
                  TextFormField(
                    controller: _provinceController,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: '省份',
                    ),
                    validator: (v) => v?.trim().isEmpty == true 
                        ? '请输入省份' 
                        : null,
                  ),
                  const SizedBox(height: 16),
                  TextFormField(
                    controller: _cityController,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: '城市',
                    ),
                    validator: (v) => v?.trim().isEmpty == true 
                        ? '请输入城市' 
                        : null,
                  ),
                  const SizedBox(height: 16),
                  TextFormField(
                    controller: _districtController,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: '区/县',
                    ),
                  ),
                  const SizedBox(height: 16),
                  TextFormField(
                    controller: _detailController,
                    maxLines: 2,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: '详细地址',
                    ),
                    validator: (v) => v?.trim().isEmpty == true 
                        ? '请输入详细地址' 
                        : null,
                  ),
                ],
              ),
            ),
            const SizedBox(height: 16),
            
            // 标签和默认设置
            ShopCard(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('标签', style: Theme.of(context).textTheme.titleMedium),
                  const SizedBox(height: 12),
                  Wrap(
                    spacing: 8,
                    children: ['家', '公司', '学校'].map((tag) {
                      return ChoiceChip(
                        label: Text(tag),
                        selected: _tag == tag,
                        onSelected: (selected) {
                          setState(() => _tag = selected ? tag : null);
                        },
                      );
                    }).toList(),
                  ),
                  const SizedBox(height: 12),
                  SwitchListTile(
                    title: const Text('设为默认地址'),
                    value: _isDefault,
                    onChanged: (v) => setState(() => _isDefault = v),
                    contentPadding: EdgeInsets.zero,
                  ),
                ],
              ),
            ),
            const SizedBox(height: 24),
            
            // 保存按钮
            ShopButton(
              label: '保存',
              icon: Icons.save,
              onPressed: _save,
            ),
          ],
        ),
      ),
    );
  }
}

这个完整实现展示了地址编辑页面的全部功能:

页面结构:

  • 收货人信息卡片
  • 地址信息卡片
  • 标签和默认设置卡片
  • 保存按钮

功能特性:

  • 支持添加和编辑模式
  • 完整的表单验证
  • 自动加载现有地址数据
  • 默认地址管理

用户体验:

  • 清晰的表单分组
  • 实时验证反馈
  • 成功提示信息
  • 便捷的保存操作

地址数据模型扩展

地址模型提供了便捷的数据操作方法。

dart 复制代码
// 地址模型的扩展功能
class Address {
  const Address({
    required this.id,
    required this.name,
    required this.phone,
    required this.province,
    required this.city,
    required this.district,
    required this.detail,
    this.isDefault = false,
    this.tag,
  });

  final String id;
  final String name;
  final String phone;
  final String province;
  final String city;
  final String district;
  final String detail;
  final bool isDefault;
  final String? tag;

  // 获取完整地址字符串
  String get fullAddress => '$province $city $district $detail';

  // 复制方法,用于创建修改后的副本
  Address copyWith({
    String? id,
    String? name,
    String? phone,
    String? province,
    String? city,
    String? district,
    String? detail,
    bool? isDefault,
    String? tag,
  }) {
    return Address(
      id: id ?? this.id,
      name: name ?? this.name,
      phone: phone ?? this.phone,
      province: province ?? this.province,
      city: city ?? this.city,
      district: district ?? this.district,
      detail: detail ?? this.detail,
      isDefault: isDefault ?? this.isDefault,
      tag: tag ?? this.tag,
    );
  }
}

这个地址模型展示了如何设计数据结构:

模型属性:

  • 包含地址的所有必要信息
  • 支持地址标签和默认标记
  • 使用 const 构造函数确保不可变性

便捷方法:

  • fullAddress 获取完整地址字符串
  • copyWith() 创建修改后的副本
  • 支持灵活的数据操作

不可变性:

  • 所有属性都是 final
  • 使用 copyWith() 创建新对象
  • 便于状态管理和调试

错误处理和用户反馈

地址编辑过程中需要处理各种错误情况。

dart 复制代码
// 错误处理和用户反馈
class AddressEditErrorHandler {
  // 显示验证错误
  static void showValidationError(
    BuildContext context,
    String message,
  ) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  // 显示成功提示
  static void showSuccessMessage(
    BuildContext context,
    String message,
  ) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.green,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  // 显示加载对话框
  static void showLoadingDialog(BuildContext context) {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => const AlertDialog(
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            CircularProgressIndicator(),
            SizedBox(height: 16),
            Text('保存中...'),
          ],
        ),
      ),
    );
  }

  // 隐藏加载对话框
  static void hideLoadingDialog(BuildContext context) {
    Navigator.of(context).pop();
  }

  // 处理保存异常
  static Future<void> handleSaveError(
    BuildContext context,
    dynamic error,
  ) async {
    showValidationError(
      context,
      '保存失败:${error.toString()}',
    );
  }
}

// 在地址编辑页面中使用
Future<void> _saveAddressWithErrorHandling() async {
  try {
    if (!_formKey.currentState!.validate()) {
      AddressEditErrorHandler.showValidationError(
        context,
        '请填写所有必填项',
      );
      return;
    }

    // 显示加载对话框
    AddressEditErrorHandler.showLoadingDialog(context);

    final appState = AppStateScope.of(context);
    final address = Address(
      id: _editingAddress?.id ?? 
          'addr_${DateTime.now().millisecondsSinceEpoch}',
      name: _nameController.text.trim(),
      phone: _phoneController.text.trim(),
      province: _provinceController.text.trim(),
      city: _cityController.text.trim(),
      district: _districtController.text.trim(),
      detail: _detailController.text.trim(),
      tag: _tag,
      isDefault: _isDefault,
    );

    // 模拟网络延迟
    await Future.delayed(const Duration(milliseconds: 500));

    if (!mounted) return;

    // 隐藏加载对话框
    AddressEditErrorHandler.hideLoadingDialog(context);

    if (_editingAddress != null) {
      appState.updateAddress(address);
    } else {
      appState.addAddress(address);
    }

    // 显示成功提示
    AddressEditErrorHandler.showSuccessMessage(
      context,
      _editingAddress != null ? '地址已更新' : '地址已添加',
    );

    // 返回上一页
    Navigator.of(context).pop();
  } catch (e) {
    if (!mounted) return;
    AddressEditErrorHandler.hideLoadingDialog(context);
    await AddressEditErrorHandler.handleSaveError(context, e);
  }
}

这个错误处理展示了如何提供良好的用户反馈:

错误提示:

  • 验证错误显示为红色 SnackBar
  • 清晰的错误信息
  • 自动消失的提示

成功反馈:

  • 成功提示显示为绿色 SnackBar
  • 确认操作完成
  • 自动返回上一页

加载状态:

  • 显示加载对话框
  • 防止用户重复操作
  • 提示用户等待

异常处理:

  • 使用 try-catch 捕获异常
  • 隐藏加载对话框
  • 显示错误信息

总结

地址编辑的实现涉及多个重要的技术点。首先是表单控制器的管理,确保资源正确释放。其次是表单验证,提供清晰的错误提示。再次是地址数据的初始化和保存,支持添加和编辑模式。最后是默认地址的管理和用户反馈,提供完整的用户体验。

这种设计确保了地址编辑的功能完整性和用户体验的流畅性。用户可以轻松添加和编辑地址、设置默认地址、获得清晰的操作反馈,整个地址编辑流程自然而直观。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
爬山算法2 小时前
Hibernate(47)Hibernate的会话范围(Scope)如何控制?
java·后端·hibernate
雨中飘荡的记忆2 小时前
Caffeine入门到实战
java
砚边数影2 小时前
AI开发依赖引入:DL4J / Java-ML 框架 Maven 坐标配置
java·数据库·人工智能·深度学习·机器学习·ai·maven
一路向北North2 小时前
nacos更改配置值后,应用提示Refresh keys changed 但是注入的值没有发生变化
java
南村群童欺我老无力.2 小时前
Flutter 框架跨平台鸿蒙开发 - 开发二维码生成器与扫描器
flutter·华为·typescript·harmonyos
42nf2 小时前
Android 根据platform.pk8和platform.x509.pem生成.jks文件
android·.pk8和.pem生成.jks
南村群童欺我老无力.2 小时前
Flutter 框架跨平台鸿蒙开发 - 喝水提醒应用开发指南
flutter·华为·harmonyos
黎雁·泠崖2 小时前
Java面向对象:this关键字+构造方法+标准JavaBean
java·开发语言·python
奋斗的小青年!!2 小时前
Flutter开发鸿蒙应用实战:位置分享组件的跨平台实现
flutter·harmonyos·鸿蒙