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 将其包装在另一个小部件中。
我知道这与您的要求不完全相同,但我认为了解解决该问题的另一种方法可能会有所帮助。
我正在使用未来的提供商在加载时显示登录页面,然后在加载时显示加载指示器。这是我未来的供应商
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 将其包装在另一个小部件中。
我知道这与您的要求不完全相同,但我认为了解解决该问题的另一种方法可能会有所帮助。