Future Provider 卡在加载状态

Future Provider Stuck In loading state

我正在使用未来的提供商在加载时显示登录页面,然后在加载时显示加载指示器。这是我未来的供应商

final loginProvider = FutureProvider.family((ref, UserInput input) =>
ref.read(authRepositoryProvider).doLogin(input.email, input.password));

在我的 UI 我有这个....

class LoginScreen extends HookWidget {
   final TextEditingController emailEditingController = TextEditingController();
   final TextEditingController passwordEditingController =
       TextEditingController();

  @override
  Widget build(BuildContext context) {
     var userInput =
     UserInput(emailEditingController.text, passwordEditingController.text);

     final login = useProvider(loginProvider(userInput));

     return login.when(
       data: (user) => Login(emailEditingController, passwordEditingController),
       loading: () => const ProgressIndication(),
       error: (error, stack) {
         if (error is DioError) {
         return Login(emailEditingController, passwordEditingController);
       } else {
          return Login(emailEditingController, passwordEditingController);
       }
     },
  );
 }
}

这是我的 doLogin 函数。

@override
  Future<dynamic> doLogin(String email, String password) async {
    try {
       final response = await _read(dioProvider)
          .post('$baseUrl/login', data: {'email': email, 'password': password});
       final data = Map<String, dynamic>.from(response.data);
     return data;
    } on DioError catch (e) {
       return BadRequestException(e.error);
    } on SocketException {
      return 'No Internet Connection';
   }
 }

我想知道为什么卡在loading状态。任何帮助将不胜感激。

首先,family 在给定输入时创建提供程序的新实例。因此,在您的实施中,只要您的文本字段发生变化,您就会生成一个新的提供者并监视该新的提供者。 这很糟糕。

在您的情况下,为了访问登录状态而保留 UserInput 没有多大意义。也就是说,在这种情况下,FamilyProvider 并不理想。

以下是您可以选择如何编写的示例。 这不是您编写它的唯一方法。 如果没有像 Firebase 那样为您处理大部分内容的 API 流式传输,它可能更容易掌握。

首先,一个StateNotifierProvider:

enum LoginState { loggedOut, loading, loggedIn, error }

class LoginStateNotifier extends StateNotifier<LoginState> {
  LoginStateNotifier(this._read) : super(LoginState.loggedOut);

  final Reader _read;
  late final Map<String, dynamic> _user;

  static final provider =
      StateNotifierProvider<LoginStateNotifier, LoginState>((ref) => LoginStateNotifier(ref.read));

  Future<void> login(String email, String password) async {
    state = LoginState.loading;
    try {
      _user = await _read(authRepositoryProvider).doLogin(email, password);
      state = LoginState.loggedIn;
    } catch (e) {
      state = LoginState.error;
    }
  }

  Map<String, dynamic> get user => _user;
}

这使我们可以手动控制登录过程的状态。它不是最优雅的,但实际上它是有效的。

接下来是登录屏幕。这是他们得到的准系统。暂时忽略 error 参数 - 稍后会被清除。

class LoginScreen extends HookWidget {
  const LoginScreen({Key? key, this.error = false}) : super(key: key);

  final bool error;

  @override
  Widget build(BuildContext context) {
    final emailController = useTextEditingController();
    final passwordController = useTextEditingController();

    return Column(
      children: [
        TextField(
          controller: emailController,
        ),
        TextField(
          controller: passwordController,
        ),
        ElevatedButton(
          onPressed: () async {
            await context.read(LoginStateNotifier.provider.notifier).login(
                  emailController.text,
                  passwordController.text,
                );
          },
          child: Text('Login'),
        ),
        if (error) Text('Error signing in'),
      ],
    );
  }
}

您会注意到我们也可以使用 useTextEditingController 挂钩来处理这些挂钩。您还可以通过 StateNotifier 看到对 login 的调用。

最后但并非最不重要的一点是,我们需要对我们奇特的新状态做一些事情。

class AuthPage extends HookWidget {
  const AuthPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final loginState = useProvider(LoginStateNotifier.provider);

    switch (loginState) {
      case LoginState.loggedOut:
        return LoginScreen();
      case LoginState.loading:
        return LoadingPage();
      case LoginState.loggedIn:
        return HomePage();
      case LoginState.error:
        return LoginScreen(error: true);
    }
  }
}

在实践中,您将希望使用 Scaffold 将其包装在另一个小部件中。

我知道这与您的要求不完全相同,但我认为了解解决该问题的另一种方法可能会有所帮助。