Flutter - 如何将用户数据传递给所有视图
Flutter - How to pass user data to all views
我是 flutter 世界和移动应用程序开发的新手,正在为如何在我的应用程序中传递用户数据而苦苦挣扎。
我已经尝试了几件事,但 none 看起来很棒,我确信我应该遵循一些最佳实践模式。
因为它使示例更容易,所以我使用 firebase 进行身份验证。
我目前有一个单独的登录路径。登录后,我希望在大多数视图中使用用户模型来检查显示内容的权限、在抽屉中显示用户信息等...
Firebase 有一个 await firebaseAuth.currentUser();
最好的做法是在您可能需要用户的任何地方调用它吗?如果是这样,打电话的最佳地点在哪里?
flutter codelab 展示了在允许写入之前对用户进行身份验证的一个很好的示例。但是,如果页面需要检查身份验证以确定要构建的内容,则异步调用不能进入 build
方法。
初始状态
我尝试过的一种方法是重写 initState 并启动获取用户的调用。当 future 完成时,我调用 setState
并更新用户。
FirebaseUser user;
@override
void initState() {
super.initState();
_getUserDetail();
}
Future<Null> _getUserDetail() async {
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
这工作得不错,但对于每个需要它的小部件来说似乎都是一个仪式。当屏幕在没有用户的情况下加载时也会出现闪烁,然后在未来完成时随用户一起更新。
通过构造函数传递用户
这也有效,但需要大量样板文件才能让用户通过所有可能需要访问它们的路由、视图和状态。此外,我们不能在转换路由时只执行 popAndPushNamed
,因为我们无法将变量传递给它。我们必须更改类似这样的路线:
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) => new MyPage(user),
));
继承的小部件
https://medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1
本文展示了使用 InheritedWidget
的一个很好的模式。当我将继承的小部件放置在 MaterialApp 级别时,当身份验证状态更改时子项不会更新(我确定我做错了)
FirebaseUser user;
Future<Null> didChangeDependency() async {
super.didChangeDependencies();
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
@override
Widget build(BuildContext context) {
return new UserContext(
user,
child: new MaterialApp(
title: 'TC Stream',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new LoginView(title: 'TC Stream Login', analytics: analytics),
routes: routes,
),
);
}
FutureBuilder
FutureBuilder 似乎也是一个不错的选择,但似乎每条路线都有很多工作要做。在下面的部分示例中,_authenticateUser()
正在获取用户并在完成时设置状态。
@override
Widget build(BuildContext context) {
return new FutureBuilder<FirebaseUser>(
future: _authenticateUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildProgressIndicator();
}
if (snapshot.connectionState == ConnectionState.done) {
return _buildPage();
}
},
);
}
对于最佳实践模式的任何建议或用于示例的资源链接,我将不胜感激。
我建议进一步调查继承的小部件;下面的代码显示了如何将它们用于异步更新数据:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(new MaterialApp(
title: 'Inherited Widgets Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('Inherited Widget Example'),
),
body: new NamePage())));
}
// Inherited widget for managing a name
class NameInheritedWidget extends InheritedWidget {
const NameInheritedWidget({
Key key,
this.name,
Widget child}) : super(key: key, child: child);
final String name;
@override
bool updateShouldNotify(NameInheritedWidget old) {
print('In updateShouldNotify');
return name != old.name;
}
static NameInheritedWidget of(BuildContext context) {
// You could also just directly return the name here
// as there's only one field
return context.inheritFromWidgetOfExactType(NameInheritedWidget);
}
}
// Stateful widget for managing name data
class NamePage extends StatefulWidget {
@override
_NamePageState createState() => new _NamePageState();
}
// State for managing fetching name data over HTTP
class _NamePageState extends State<NamePage> {
String name = 'Placeholder';
// Fetch a name asynchonously over HTTP
_get() async {
var res = await http.get('https://jsonplaceholder.typicode.com/users');
var name = json.decode(res.body)[0]['name'];
setState(() => this.name = name);
}
@override
void initState() {
super.initState();
_get();
}
@override
Widget build(BuildContext context) {
return new NameInheritedWidget(
name: name,
child: const IntermediateWidget()
);
}
}
// Intermediate widget to show how inherited widgets
// can propagate changes down the widget tree
class IntermediateWidget extends StatelessWidget {
// Using a const constructor makes the widget cacheable
const IntermediateWidget();
@override
Widget build(BuildContext context) {
return new Center(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: const NameWidget()));
}
}
class NameWidget extends StatelessWidget {
const NameWidget();
@override
Widget build(BuildContext context) {
final inheritedWidget = NameInheritedWidget.of(context);
return new Text(
inheritedWidget.name,
style: Theme.of(context).textTheme.display1,
);
}
}
因为这个问题我又遇到了一个问题you can check it out here
所以我想出的解决方案有点乱,我单独创建了一个Instance dart页面,然后导入到每个页面。
GoogleSignInAccount Guser = googleSignIn.currentUser;
FirebaseUser Fuser;
我在登录时将用户存储在那里,并检查每个 StateWidget 是否为空
Future<Null> _ensureLoggedIn() async {
if (Guser == null) Guser = await googleSignIn.signInSilently();
if (Fuser == null) {
await googleSignIn.signIn();
analytics.logLogin();
}
if (await auth.currentUser() == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
这是我的旧代码,我已经在我当前的应用程序中对其进行了清理,但我现在手边没有该代码。只需检查空用户并重新登录
我也为大多数 Firebase 实例执行了此操作,因为我的应用程序中有超过 3 个页面,而且 Inherited Widgets 的工作量太大了
对于我懒惰的方法,
我只是创建新文件,如 userdata.dart,然后在其上放置任何变量,例如 dynamic Profile = null
里面userdata.dart
//only put this or anything u want.
dynamic Profile = null;
在startingpage.dart
//import that file
import '../userdata.dart';
class startingpage extends ...{
...
//set data to store..
Profile = 'user profile';
...
}
使用刚刚声明和使用的数据
anotherpage.dart
//import that file
import '../userdata.dart';
class anotherpage extends...{
...
}
class .. State ...{
...
//set the data to variable
dynamic userdata = Profile;
print('this is my lazy pass data' + userdata.toString());
...
}
我更喜欢将服务与定位器一起使用,使用 Flutter get_it。
如果您愿意,可以使用缓存数据创建一个 UserService:
class UserService {
final Firestore _db = Firestore.instance;
final String _collectionName = 'users';
CollectionReference _ref;
User _cachedUser; //<----- Cached Here
UserService() {
this._ref = _db.collection(_collectionName);
}
User getCachedUser() {
return _cachedUser;
}
Future<User> getUser(String id) async {
DocumentSnapshot doc = await _ref.document(id).get();
if (!doc.exists) {
log("UserService.getUser(): Empty companyID ($id)");
return null;
}
_cachedUser = User.fromDocument(doc.data, doc.documentID);
return _cachedUser;
}
}
然后创建一个定位器
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => new UserService());
}
并在main()中实例化
void main() {
setupLocator();
new Routes();
}
就是这样!您可以使用以下方式在任何地方调用您的 Service + cachedData:
.....
UserService _userService = locator<UserService>();
@override
void initState() {
super.initState();
_user = _userService.getCachedUser();
}
您可以使用 GetX 包检查用户是否登录、获取用户数据并在整个应用程序中访问这些数据
我是 flutter 世界和移动应用程序开发的新手,正在为如何在我的应用程序中传递用户数据而苦苦挣扎。
我已经尝试了几件事,但 none 看起来很棒,我确信我应该遵循一些最佳实践模式。
因为它使示例更容易,所以我使用 firebase 进行身份验证。 我目前有一个单独的登录路径。登录后,我希望在大多数视图中使用用户模型来检查显示内容的权限、在抽屉中显示用户信息等...
Firebase 有一个 await firebaseAuth.currentUser();
最好的做法是在您可能需要用户的任何地方调用它吗?如果是这样,打电话的最佳地点在哪里?
flutter codelab 展示了在允许写入之前对用户进行身份验证的一个很好的示例。但是,如果页面需要检查身份验证以确定要构建的内容,则异步调用不能进入 build
方法。
初始状态
我尝试过的一种方法是重写 initState 并启动获取用户的调用。当 future 完成时,我调用 setState
并更新用户。
FirebaseUser user;
@override
void initState() {
super.initState();
_getUserDetail();
}
Future<Null> _getUserDetail() async {
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
这工作得不错,但对于每个需要它的小部件来说似乎都是一个仪式。当屏幕在没有用户的情况下加载时也会出现闪烁,然后在未来完成时随用户一起更新。
通过构造函数传递用户
这也有效,但需要大量样板文件才能让用户通过所有可能需要访问它们的路由、视图和状态。此外,我们不能在转换路由时只执行 popAndPushNamed
,因为我们无法将变量传递给它。我们必须更改类似这样的路线:
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) => new MyPage(user),
));
继承的小部件
https://medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1
本文展示了使用 InheritedWidget
的一个很好的模式。当我将继承的小部件放置在 MaterialApp 级别时,当身份验证状态更改时子项不会更新(我确定我做错了)
FirebaseUser user;
Future<Null> didChangeDependency() async {
super.didChangeDependencies();
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
@override
Widget build(BuildContext context) {
return new UserContext(
user,
child: new MaterialApp(
title: 'TC Stream',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new LoginView(title: 'TC Stream Login', analytics: analytics),
routes: routes,
),
);
}
FutureBuilder
FutureBuilder 似乎也是一个不错的选择,但似乎每条路线都有很多工作要做。在下面的部分示例中,_authenticateUser()
正在获取用户并在完成时设置状态。
@override
Widget build(BuildContext context) {
return new FutureBuilder<FirebaseUser>(
future: _authenticateUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildProgressIndicator();
}
if (snapshot.connectionState == ConnectionState.done) {
return _buildPage();
}
},
);
}
对于最佳实践模式的任何建议或用于示例的资源链接,我将不胜感激。
我建议进一步调查继承的小部件;下面的代码显示了如何将它们用于异步更新数据:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(new MaterialApp(
title: 'Inherited Widgets Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('Inherited Widget Example'),
),
body: new NamePage())));
}
// Inherited widget for managing a name
class NameInheritedWidget extends InheritedWidget {
const NameInheritedWidget({
Key key,
this.name,
Widget child}) : super(key: key, child: child);
final String name;
@override
bool updateShouldNotify(NameInheritedWidget old) {
print('In updateShouldNotify');
return name != old.name;
}
static NameInheritedWidget of(BuildContext context) {
// You could also just directly return the name here
// as there's only one field
return context.inheritFromWidgetOfExactType(NameInheritedWidget);
}
}
// Stateful widget for managing name data
class NamePage extends StatefulWidget {
@override
_NamePageState createState() => new _NamePageState();
}
// State for managing fetching name data over HTTP
class _NamePageState extends State<NamePage> {
String name = 'Placeholder';
// Fetch a name asynchonously over HTTP
_get() async {
var res = await http.get('https://jsonplaceholder.typicode.com/users');
var name = json.decode(res.body)[0]['name'];
setState(() => this.name = name);
}
@override
void initState() {
super.initState();
_get();
}
@override
Widget build(BuildContext context) {
return new NameInheritedWidget(
name: name,
child: const IntermediateWidget()
);
}
}
// Intermediate widget to show how inherited widgets
// can propagate changes down the widget tree
class IntermediateWidget extends StatelessWidget {
// Using a const constructor makes the widget cacheable
const IntermediateWidget();
@override
Widget build(BuildContext context) {
return new Center(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: const NameWidget()));
}
}
class NameWidget extends StatelessWidget {
const NameWidget();
@override
Widget build(BuildContext context) {
final inheritedWidget = NameInheritedWidget.of(context);
return new Text(
inheritedWidget.name,
style: Theme.of(context).textTheme.display1,
);
}
}
因为这个问题我又遇到了一个问题you can check it out here 所以我想出的解决方案有点乱,我单独创建了一个Instance dart页面,然后导入到每个页面。
GoogleSignInAccount Guser = googleSignIn.currentUser;
FirebaseUser Fuser;
我在登录时将用户存储在那里,并检查每个 StateWidget 是否为空
Future<Null> _ensureLoggedIn() async {
if (Guser == null) Guser = await googleSignIn.signInSilently();
if (Fuser == null) {
await googleSignIn.signIn();
analytics.logLogin();
}
if (await auth.currentUser() == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
这是我的旧代码,我已经在我当前的应用程序中对其进行了清理,但我现在手边没有该代码。只需检查空用户并重新登录
我也为大多数 Firebase 实例执行了此操作,因为我的应用程序中有超过 3 个页面,而且 Inherited Widgets 的工作量太大了
对于我懒惰的方法, 我只是创建新文件,如 userdata.dart,然后在其上放置任何变量,例如 dynamic Profile = null
里面userdata.dart
//only put this or anything u want.
dynamic Profile = null;
在startingpage.dart
//import that file
import '../userdata.dart';
class startingpage extends ...{
...
//set data to store..
Profile = 'user profile';
...
}
使用刚刚声明和使用的数据 anotherpage.dart
//import that file
import '../userdata.dart';
class anotherpage extends...{
...
}
class .. State ...{
...
//set the data to variable
dynamic userdata = Profile;
print('this is my lazy pass data' + userdata.toString());
...
}
我更喜欢将服务与定位器一起使用,使用 Flutter get_it。
如果您愿意,可以使用缓存数据创建一个 UserService:
class UserService {
final Firestore _db = Firestore.instance;
final String _collectionName = 'users';
CollectionReference _ref;
User _cachedUser; //<----- Cached Here
UserService() {
this._ref = _db.collection(_collectionName);
}
User getCachedUser() {
return _cachedUser;
}
Future<User> getUser(String id) async {
DocumentSnapshot doc = await _ref.document(id).get();
if (!doc.exists) {
log("UserService.getUser(): Empty companyID ($id)");
return null;
}
_cachedUser = User.fromDocument(doc.data, doc.documentID);
return _cachedUser;
}
}
然后创建一个定位器
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => new UserService());
}
并在main()中实例化
void main() {
setupLocator();
new Routes();
}
就是这样!您可以使用以下方式在任何地方调用您的 Service + cachedData:
.....
UserService _userService = locator<UserService>();
@override
void initState() {
super.initState();
_user = _userService.getCachedUser();
}
您可以使用 GetX 包检查用户是否登录、获取用户数据并在整个应用程序中访问这些数据