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

Flutter中Appwrite创建会话随机抛出Concurrent Modification异常

问题原因
  • 核心错误:每次调用登录方法时都重新初始化Client和Account实例。Appwrite SDK内部维护了请求拦截器、响应回调、状态监听器的可增长列表,重复创建的多个Client实例在异步请求返回时,会同时对监听器列表做增删操作,Dart异步模型在迭代列表过程中如果检测到列表被修改,就会抛出Concurrent modification during iteration异常,因为异步返回时机完全随机,所以故障没有明显规律。
  • 次要问题1:将TextEditingController定义在StatelessWidget的build方法中,每次页面重建都会生成新的控制器,触发不必要的组件重建,放大了多实例带来的并发风险。
  • 次要问题2:没有做请求防重复处理,用户快速多次点击登录按钮时,会同时触发多个登录请求、生成多组Client实例,进一步提升并发修改的触发概率。
  • 次要问题3:异步回调中执行导航操作前没有校验组件挂载状态,页面提前销毁时可能触发SDK回调链路的异常中断,意外修改内部迭代中的列表。
解决方案

1. 全局单例初始化Appwrite实例

新建独立的服务管理文件,全局只初始化一次Client和相关服务实例,所有接口调用复用同一个实例,从根源避免多实例并发修改内部列表的问题。

// appwrite_service.dart
import 'package:appwrite/appwrite.dart';

class AppwriteService {
  static final AppwriteService _instance = AppwriteService._internal();
  late final Client client;
  late final Account account;

  factory AppwriteService() => _instance;

  AppwriteService._internal() {
    client = Client()
      .setEndpoint('https://jeff.vmsimple.com/v1')
      .setProject('jeffs-timekeeper');
    account = Account(client);
    // 其他Appwrite服务(Databases、Storage等)也在这里统一初始化
  }
}

2. 重构登录页为StatefulWidget

将文本控制器移到State生命周期中管理,避免build方法重复生成新控制器,同时增加登录防重复逻辑、组件挂载状态校验。

class LoginScreen extends StatefulWidget {
  const LoginScreen({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  late final TextEditingController _emailController;
  late final TextEditingController _passwordController;
  bool _isLoginLoading = false;

  @override
  void initState() {
    super.initState();
    _emailController = TextEditingController();
    _passwordController = TextEditingController();
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  Future<void> _handleLogin() async {
    // 拦截重复点击
    if (_isLoginLoading) return;
    setState(() => _isLoginLoading = true);

    try {
      // 复用全局单例的Account实例
      final response = await AppwriteService().account.createSession(
        email: _emailController.text.trim(),
        password: _passwordController.text,
      );
      print(response);
      // 页面销毁后不再执行导航操作
      if (!mounted) return;
      Navigator.popAndPushNamed(context, '/dashboardScreen');
    } catch (error) {
      print(error);
      if (!mounted) return;
      // 可按需增加错误提示
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('登录失败,请检查账号密码')),
      );
    } finally {
      if (mounted) {
        setState(() => _isLoginLoading = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Login',
              style: Theme.of(context).textTheme.headline4,
            ),
            Container(
              margin: const EdgeInsets.fromLTRB(25, 25, 25, 12.5),
              child: TextField(
                controller: _emailController,
                obscureText: false,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Username',
                ),
              ),
            ),
            Container(
              margin: const EdgeInsets.fromLTRB(25, 12.5, 25, 25),
              child: TextField(
                controller: _passwordController,
                obscureText: true,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Password',
                ),
              ),
            ),
            Container(
              margin: const EdgeInsets.only(bottom: 25),
              child: Row(
                children: [
                  Expanded(
                    child: Column(
                      children: [
                        ElevatedButton(
                          // 加载状态下禁用按钮点击
                          onPressed: _isLoginLoading ? null : _handleLogin,
                          child: _isLoginLoading
                              ? const SizedBox(
                                  width: 16,
                                  height: 16,
                                  child: CircularProgressIndicator(
                                    strokeWidth: 2,
                                    color: Colors.white,
                                  ),
                                )
                              : const Text('Login'),
                        )
                      ],
                    ),
                  ),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

3. 其他注意事项

  • 项目中所有Appwrite接口调用,都必须复用全局单例的Client实例初始化对应服务,禁止在接口方法中临时新建Client。
  • 所有涉及页面跳转、状态更新的异步回调,执行前必须校验mounted属性,避免组件销毁后执行上下文相关操作触发SDK链路异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 02:24:14