Flutter:如何从不是小部件的类中调用 SnackBar
Posted
技术标签:
【中文标题】Flutter:如何从不是小部件的类中调用 SnackBar【英文标题】:Flutter: How to call a SnackBar form a class that is not a widget 【发布时间】:2020-05-19 18:20:51 【问题描述】:我从 Flutter 开始,我制作了一个使用 REST API 管理登录屏幕的简单应用。
我正在使用 http 包和 http_interceptor 包来拦截并发送标头中的令牌。
问题是......我可以毫无问题地使用拦截器捕获错误。但是,有什么方法可以使用来自我的拦截器类的全局快餐栏,它可以“通知”并将用户重定向到显示应用程序中任何错误的登录屏幕,例如,当令牌无效时?
这是我的拦截器类:
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.
【问题讨论】:
通过你的类构造函数传递Scaffold
Context
怎么样?
怎么样?该课程没有从任何小部件调用...你能解释一下吗?感谢您的回答!
你的代码中ApiInterceptor
在哪里使用?你的树下应该有某个地方可以为此传递BuildContext
。
好的,在某些提供程序上调用了 ApiInterceptor。我会用一个例子来更新这个问题......
完成@odonckers!感谢朋友的帮助。
【参考方案1】:
更新答案
原始答案将BuildContext
传递到您的ChangeNotifier
服务中,该服务在技术上有效,但在查看后我意识到它非常不专业。这是因为使用Provider
或服务的整个概念是将小部件构建和后台功能分开。传递BuildContext
并从服务内部创建Snackbar
并不是很好。 Bellow 是一项更专业的工作,需要更多的工作来解决它,但从长远来看更加灵活。
想法
因此,所有Widget
代码都包含在您用于 UI 和 UX 的类中,您需要在类中具有某种类型的函数,但只能从您的 ApiInterceptor
调用。为此,您将使用可以应用于变量的称为 typedef
的东西。
第 1 步:创建typedef
您的typedef
应该在类之外创建,但仍然在您要应用它的主文件中,最好是在包含ApiInterceptor
的文件中。
typedef void OnInterceptError (String errorMessage);
如果您从未使用过任何语言的typedef
,您可能会非常困惑。所做的只是创建一个函数类型,它返回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
类在声明时没有任何输入变量,因此您将在顶部添加以下内容。
ApiInterceptor(
@required this.context,
) : assert(context != null);
final BuildContext context;
现在,每次在代码库中访问您的类时,IDE 都会通知您缺少变量。
第 2 步:在 Auth
中需要 BuildContext
很遗憾,您将不得不对您的 Auth
提供者执行完全相同的操作。因为它们几乎是相同的程序,所以我不会像最后一步那样给你同样的独白。以下是您必须添加到 Auth
类开头的内容。
Auth(
@required this.context,
) : assert(context != null);
final BuildContext context;
第 3 步:在每个必需的情况下传递 BuildContext
您可能会明白这一点,您的 IDE 会为您完成大部分工作!以下是您所有课程的完整代码。
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
。
我希望这会有所帮助,并使您的一天有点更轻松。
【讨论】:
我在尝试实现您的代码时遇到问题我的朋友....当我尝试将上下文传递给ApiInterceptor
时,IDE 出现错误:“在初始化程序中只能访问静态成员。”这里:客户端 http = HttpClientWithInterceptor.build(interceptors: [ApiInterceptor(context: context), ]);
我已经解决了将客户端 (Client http = ...
) 移动到 Future 函数的问题。但是当我尝试它时,我有另一个错误说找不到任何脚手架来显示 SnackBar... 在 main.dart 中,在孩子中我有脚手架。
哦...我忘了你的BuildContext
必须有一个活跃的Scaffold
在你通过它之前。所以我会尝试将您的MultiProvider
移到MaterialApp
内的树上。如果这不起作用,请尝试将其移到 Scaffold
内部更远一些,这样更有可能起作用。
同样的错误我的朋友。请看我的更新二。感谢您花时间帮助我!我真的很感激。
我刚刚用一个更好的解决方案更新了我的答案。在看了你的评论,然后看了我的回答之后,我意识到我向你展示的方式是多么不专业。从技术上讲,它可以工作,但不可扩展,并且不适用于一类中的 UI 概念和另一类中的服务。希望上面的答案能解决问题。以上是关于Flutter:如何从不是小部件的类中调用 SnackBar的主要内容,如果未能解决你的问题,请参考以下文章
使用 Flutter,如何在单独的登录页面小部件中显示在 AuthService 类中捕获的 Firebase Auth 错误消息?