Flutter:如何从 class 调用不是小部件的 SnackBar
Flutter: How to call a SnackBar form a class that is not a widget
我从 flutter 开始,我制作了一个使用 REST 管理登录屏幕的简单应用程序 API。
我正在使用 http 包和 http_interceptor 包来拦截和发送 headers 中的令牌。
问题是...我可以毫无问题地用拦截器捕获错误。但是,有任何方法可以使用来自我的拦截器 class 的全局小吃店 "notify" 并将用户重定向到显示应用程序中的任何错误的登录屏幕,例如,当令牌无效时?
这是我的拦截器class:
class ApiInterceptor with ChangeNotifier implements InterceptorContract {
final storage = new FlutterSecureStorage();
@override
Future<RequestData> interceptRequest({RequestData data}) async {
[...] // here is the request interceptor
return data;
}
// The response interceptor:
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
if (data.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
// here i want to send the notification to a snackBar
// then, i want to redirect the user to the login screen
}
return data;
}
}
[更新一]
这是我使用的提供商。在此提供程序中,我使用拦截器。
import 'dart:convert';
import 'package:cadsanjuan_movil/models/http_exception.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart';
import 'package:http_interceptor/http_interceptor.dart';
import '../config/http_interceptor.dart';
import '../config/.env.dart' as config;
class Auth with ChangeNotifier {
String _endpoint = 'auth';
final storage = new FlutterSecureStorage();
// Http Interceptor
Client http = HttpClientWithInterceptor.build(interceptors: [
ApiInterceptor(),
]);
Future singup(String email, String password) async {
final url = "${config.apiBaseUrl}/$_endpoint/signin";
try {
final response = await http.post(url,
body: json.encode({'email': email, 'password': password}));
final decodedResponse = json.decode(response.body);
/* if (response.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
} */
await storage.write(key: 'token', value: decodedResponse['token']);
await storage.write(key: 'user', value: decodedResponse['user']);
await storage.write(key: 'email', value: decodedResponse['email']);
await storage.write(
key: 'employeeId', value: decodedResponse['employeeId'].toString());
//notifyListeners();
} catch (error) {
throw error;
}
}
}
在我的 main.dart 上使用 MultipleProvider
小部件调用这些提供程序:
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ApiInterceptor(),
),
ChangeNotifierProvider.value(
value: Auth(),
),
ChangeNotifierProvider.value(
value: TurnActive(),
),
],
child: MaterialApp(
.
.
.
[更新二]
这是 main.dart
更新...但仍然无法正常工作。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final storage = new FlutterSecureStorage();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CAD App',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: Scaffold(
body: MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ApiInterceptor(context: context),
),
ChangeNotifierProvider.value(
value: Auth(context: context),
),
ChangeNotifierProvider.value(
value: TurnActive(context: context),
),
],
child: FutureBuilder(
future: storage.read(key: "token"),
builder: (context, storedKey) {
if (!storedKey.hasData) {
return LoadingData(text: 'Por favor espere...');
} else {
return storedKey.data == null
? LoginPage()
: InitialLoadingPage();
}
},
),
),
),
);
}
}
在我的拦截器上:
.
.
.
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(decodedResponse['error']),
));
.
.
.
错误是:
Scaffold.of() called with a context that does not contain a Scaffold.
更新答案
最初的答案将 BuildContext
传递到您的 ChangeNotifier
服务中,这在技术上是可行的,但在查看之后我意识到它非常不专业。这是因为使用 Provider
或服务的整个概念是将小部件构建和后台功能分开。从服务内部传递 BuildContext
并创建 Snackbar
并不是很好。 Bellow 是一个更专业的,稍微多一点的工作来绕过它,但在长 运行.
中更灵活
想法
因此,所有 Widget
代码都包含在您用于 UI 和 UX 的 class 中,您需要在class 但只能从您的 ApiInterceptor
调用。为此,您将使用可以应用于变量的所谓 typedef
。
第 1 步:创建 typedef
您的 typedef
应该在 class 之外创建,但仍然在您要应用它的主文件中,最好是在包含 ApiInterceptor
的文件中。
typedef void OnInterceptError (String errorMessage);
如果您从未使用过任何语言的 typedef
,您可能会感到非常困惑。所有这一切都在创建一个函数类型,即 returns void
,并采用 String
作为输入。
第 2 步:在 ApiInterceptor
中使用 OnInterceptError
ApiInterceptor({
@required this.interceptError,
}) : assert(interceptError != null);
final OnInterceptError this.interceptError;
// Response interceptor
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
if (data.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
// Run `interceptError` to send the notification to a
// `Snackbar`
interceptError(decodedResponse['error']);
}
return data;
}
设置好之后,终于可以进入到好的部分了:设置UI!!!
第 3 步:创建 OnInterceptError
函数...
现在您已经有了函数所在的位置 运行,您需要在函数所在的位置创建... 功能.
无论您在何处实施此 ApiInterceptor
服务,您现在都应该传递一些内容以达到以下效果。
ApiInterceptor(
interceptError: (String errorMessage) {
// Show the `Snackbar` from here, which should have
// access to the `BuildContext` to do so and use
// `interceptError` to create the message for the
// `Snackbar`, if you'd like to do so.
print(interceptError);
}
);
起初看起来真的很复杂,但它确实是一种很好的做事方式,因为它使您的服务与 UI 分开。如果您想参考或仍想使用该方法,下面是原始答案。
原回答
遗憾的是,由于 Dart 的工作方式,抓住 BuildContext
可能有点困难,但 100% 有可能。我将引导您完成这些步骤:
第 1 步:在 ApiInterceptor
中要求 BuildContext
目前您的 ApiInterceptor
class 声明时没有任何输入变量,因此您需要将以下内容添加到顶部的 class。
ApiInterceptor({
@required this.context,
}) : assert(context != null);
final BuildContext context;
现在,每次在您的代码库中访问您的 class 时,IDE 都会通知您缺少变量。
第 2 步:在 Auth
中要求 BuildContext
遗憾的是,您将不得不对 Auth
提供者执行完全相同的操作。由于它们几乎是相同的过程,因此我将避免与最后一步相同的独白。以下是您必须添加到 Auth
class.
开头的内容
Auth({
@required this.context,
}) : assert(context != null);
final BuildContext context;
第 3 步:在每个需要的情况下通过 BuildContext
您或许可以解决这个问题,您的 IDE 会为您完成大部分工作!以下是所有 classes 的完整代码。
class ApiInterceptor with ChangeNotifier implements InterceptorContract {
ApiInterceptor({
@required this.context,
}) : assert(context != null);
final BuildContext context;
final storage = new FlutterSecureStorage();
@override
Future<RequestData> interceptRequest({RequestData data}) async {
[...] // here is the request interceptor
return data;
}
// The response interceptor:
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
if (data.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
// here i want to send the notification to a snackBar
// then, i want to redirect the user to the login screen
}
return data;
}
}
class Auth with ChangeNotifier {
Auth({
@required this.context,
}) : assert(context != null);
final BuildContext context;
String _endpoint = 'auth';
final storage = new FlutterSecureStorage();
Future singup(String email, String password) async {
// Http Interceptor
Client http = HttpClientWithInterceptor.build(interceptors: [
ApiInterceptor(context: context),
]);
final url = "${config.apiBaseUrl}/$_endpoint/signin";
try {
final response = await http.post(url,
body: json.encode({'email': email, 'password': password}));
final decodedResponse = json.decode(response.body);
/* if (response.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
} */
await storage.write(key: 'token', value: decodedResponse['token']);
await storage.write(key: 'user', value: decodedResponse['user']);
await storage.write(key: 'email', value: decodedResponse['email']);
await storage.write(
key: 'employeeId', value: decodedResponse['employeeId'].toString());
//notifyListeners();
} catch (error) {
throw error;
}
}
}
当然,您的 main()
输出:
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
home: Builder(
builder: (BuildContext context) => MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ApiInterceptor(context: context),
),
ChangeNotifierProvider.value(
value: Auth(context: context),
),
ChangeNotifierProvider.value(
value: TurnActive(),
),
],
child: /* CHILD!!! */,
),
),
),
);
}
确保Builder
在树中Scaffold
下面,否则在调用Scaffold.of(context)
时无法识别Scaffold
。
我希望这对您有所帮助,让您的一天变得一点更轻松。
我从 flutter 开始,我制作了一个使用 REST 管理登录屏幕的简单应用程序 API。
我正在使用 http 包和 http_interceptor 包来拦截和发送 headers 中的令牌。
问题是...我可以毫无问题地用拦截器捕获错误。但是,有任何方法可以使用来自我的拦截器 class 的全局小吃店 "notify" 并将用户重定向到显示应用程序中的任何错误的登录屏幕,例如,当令牌无效时?
这是我的拦截器class:
class ApiInterceptor with ChangeNotifier implements InterceptorContract {
final storage = new FlutterSecureStorage();
@override
Future<RequestData> interceptRequest({RequestData data}) async {
[...] // here is the request interceptor
return data;
}
// The response interceptor:
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
if (data.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
// here i want to send the notification to a snackBar
// then, i want to redirect the user to the login screen
}
return data;
}
}
[更新一]
这是我使用的提供商。在此提供程序中,我使用拦截器。
import 'dart:convert';
import 'package:cadsanjuan_movil/models/http_exception.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart';
import 'package:http_interceptor/http_interceptor.dart';
import '../config/http_interceptor.dart';
import '../config/.env.dart' as config;
class Auth with ChangeNotifier {
String _endpoint = 'auth';
final storage = new FlutterSecureStorage();
// Http Interceptor
Client http = HttpClientWithInterceptor.build(interceptors: [
ApiInterceptor(),
]);
Future singup(String email, String password) async {
final url = "${config.apiBaseUrl}/$_endpoint/signin";
try {
final response = await http.post(url,
body: json.encode({'email': email, 'password': password}));
final decodedResponse = json.decode(response.body);
/* if (response.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
} */
await storage.write(key: 'token', value: decodedResponse['token']);
await storage.write(key: 'user', value: decodedResponse['user']);
await storage.write(key: 'email', value: decodedResponse['email']);
await storage.write(
key: 'employeeId', value: decodedResponse['employeeId'].toString());
//notifyListeners();
} catch (error) {
throw error;
}
}
}
在我的 main.dart 上使用 MultipleProvider
小部件调用这些提供程序:
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ApiInterceptor(),
),
ChangeNotifierProvider.value(
value: Auth(),
),
ChangeNotifierProvider.value(
value: TurnActive(),
),
],
child: MaterialApp(
.
.
.
[更新二]
这是 main.dart
更新...但仍然无法正常工作。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final storage = new FlutterSecureStorage();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CAD App',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: Scaffold(
body: MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ApiInterceptor(context: context),
),
ChangeNotifierProvider.value(
value: Auth(context: context),
),
ChangeNotifierProvider.value(
value: TurnActive(context: context),
),
],
child: FutureBuilder(
future: storage.read(key: "token"),
builder: (context, storedKey) {
if (!storedKey.hasData) {
return LoadingData(text: 'Por favor espere...');
} else {
return storedKey.data == null
? LoginPage()
: InitialLoadingPage();
}
},
),
),
),
);
}
}
在我的拦截器上:
.
.
.
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(decodedResponse['error']),
));
.
.
.
错误是:
Scaffold.of() called with a context that does not contain a Scaffold.
更新答案
最初的答案将 BuildContext
传递到您的 ChangeNotifier
服务中,这在技术上是可行的,但在查看之后我意识到它非常不专业。这是因为使用 Provider
或服务的整个概念是将小部件构建和后台功能分开。从服务内部传递 BuildContext
并创建 Snackbar
并不是很好。 Bellow 是一个更专业的,稍微多一点的工作来绕过它,但在长 运行.
想法
因此,所有 Widget
代码都包含在您用于 UI 和 UX 的 class 中,您需要在class 但只能从您的 ApiInterceptor
调用。为此,您将使用可以应用于变量的所谓 typedef
。
第 1 步:创建 typedef
您的 typedef
应该在 class 之外创建,但仍然在您要应用它的主文件中,最好是在包含 ApiInterceptor
的文件中。
typedef void OnInterceptError (String errorMessage);
如果您从未使用过任何语言的 typedef
,您可能会感到非常困惑。所有这一切都在创建一个函数类型,即 returns void
,并采用 String
作为输入。
第 2 步:在 ApiInterceptor
中使用 OnInterceptError
ApiInterceptor({
@required this.interceptError,
}) : assert(interceptError != null);
final OnInterceptError this.interceptError;
// Response interceptor
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
if (data.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
// Run `interceptError` to send the notification to a
// `Snackbar`
interceptError(decodedResponse['error']);
}
return data;
}
设置好之后,终于可以进入到好的部分了:设置UI!!!
第 3 步:创建 OnInterceptError
函数...
现在您已经有了函数所在的位置 运行,您需要在函数所在的位置创建... 功能.
无论您在何处实施此 ApiInterceptor
服务,您现在都应该传递一些内容以达到以下效果。
ApiInterceptor(
interceptError: (String errorMessage) {
// Show the `Snackbar` from here, which should have
// access to the `BuildContext` to do so and use
// `interceptError` to create the message for the
// `Snackbar`, if you'd like to do so.
print(interceptError);
}
);
起初看起来真的很复杂,但它确实是一种很好的做事方式,因为它使您的服务与 UI 分开。如果您想参考或仍想使用该方法,下面是原始答案。
原回答
遗憾的是,由于 Dart 的工作方式,抓住 BuildContext
可能有点困难,但 100% 有可能。我将引导您完成这些步骤:
第 1 步:在 ApiInterceptor
中要求 BuildContext
目前您的 ApiInterceptor
class 声明时没有任何输入变量,因此您需要将以下内容添加到顶部的 class。
ApiInterceptor({
@required this.context,
}) : assert(context != null);
final BuildContext context;
现在,每次在您的代码库中访问您的 class 时,IDE 都会通知您缺少变量。
第 2 步:在 Auth
中要求 BuildContext
遗憾的是,您将不得不对 Auth
提供者执行完全相同的操作。由于它们几乎是相同的过程,因此我将避免与最后一步相同的独白。以下是您必须添加到 Auth
class.
Auth({
@required this.context,
}) : assert(context != null);
final BuildContext context;
第 3 步:在每个需要的情况下通过 BuildContext
您或许可以解决这个问题,您的 IDE 会为您完成大部分工作!以下是所有 classes 的完整代码。
class ApiInterceptor with ChangeNotifier implements InterceptorContract {
ApiInterceptor({
@required this.context,
}) : assert(context != null);
final BuildContext context;
final storage = new FlutterSecureStorage();
@override
Future<RequestData> interceptRequest({RequestData data}) async {
[...] // here is the request interceptor
return data;
}
// The response interceptor:
@override
Future<ResponseData> interceptResponse({ResponseData data}) async {
final decodedResponse = json.decode(data.body);
if (data.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
// here i want to send the notification to a snackBar
// then, i want to redirect the user to the login screen
}
return data;
}
}
class Auth with ChangeNotifier {
Auth({
@required this.context,
}) : assert(context != null);
final BuildContext context;
String _endpoint = 'auth';
final storage = new FlutterSecureStorage();
Future singup(String email, String password) async {
// Http Interceptor
Client http = HttpClientWithInterceptor.build(interceptors: [
ApiInterceptor(context: context),
]);
final url = "${config.apiBaseUrl}/$_endpoint/signin";
try {
final response = await http.post(url,
body: json.encode({'email': email, 'password': password}));
final decodedResponse = json.decode(response.body);
/* if (response.statusCode >= 400) {
throw HttpException(decodedResponse['error']);
} */
await storage.write(key: 'token', value: decodedResponse['token']);
await storage.write(key: 'user', value: decodedResponse['user']);
await storage.write(key: 'email', value: decodedResponse['email']);
await storage.write(
key: 'employeeId', value: decodedResponse['employeeId'].toString());
//notifyListeners();
} catch (error) {
throw error;
}
}
}
当然,您的 main()
输出:
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
home: Builder(
builder: (BuildContext context) => MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ApiInterceptor(context: context),
),
ChangeNotifierProvider.value(
value: Auth(context: context),
),
ChangeNotifierProvider.value(
value: TurnActive(),
),
],
child: /* CHILD!!! */,
),
),
),
);
}
确保Builder
在树中Scaffold
下面,否则在调用Scaffold.of(context)
时无法识别Scaffold
。
我希望这对您有所帮助,让您的一天变得一点更轻松。