Die*_*ina 2 provider-model flutter flutter-provider
我正在 Flutter 中构建我的第一个大型应用程序,也是我需要状态管理的第一个应用程序,所以我转向了 Provider,这是用于状态管理的推荐包。但是,我在 main.dart 文件和树下声明我的提供者时遇到了一些问题,我想进行更改并与其中一个提供者进行交互,但无论我尝试什么解决方案,我都会遇到相同的错误:“已尝试从窗口小部件树外部收听提供者公开的值。”。即使根据颤振检查器,我尝试对提供程序进行更改的小部件位于小部件树内部(“主屏幕”屏幕来自我更新提供程序的位置),我也会收到此错误。

下面我也分享我的代码:main.dart:
import 'package:flutter/material.dart';
import 'package:tic_tac_2/screens/welcome_screen.dart';
import 'package:provider/provider.dart';
import 'package:tic_tac_2/models/restaurants_data.dart';
import 'package:tic_tac_2/models/promotions_data.dart';
import 'package:tic_tac_2/models/user.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<User>(create: (context) => User(),),
ChangeNotifierProvider<RestaurantsData>(create: (context) => RestaurantsData(),),
ChangeNotifierProvider<PromotionsData>(create: (context) => PromotionsData(),),
],
child: MaterialApp(
title: 'Tic Tac',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: WelcomeScreen(),
),
);
}
}
Run Code Online (Sandbox Code Playgroud)
欢迎_screen.dart:
import 'package:flutter/material.dart';
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:tic_tac_2/components/rounded_button.dart';
import 'login_screen.dart';
import 'register_screen.dart';
class WelcomeScreen extends StatelessWidget {
static const String id = 'welcome_screen';
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xff000080),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
Hero(
tag: 'logo',
child: Container(
child: Image.asset('images/pin.png'),
height: 60.0,
),
),
TypewriterAnimatedTextKit(
text: ['Tic Tac'],
textStyle: TextStyle(
fontWeight: FontWeight.w900,
fontSize: 45.0,
color: Colors.white
),
),
],
),
SizedBox(
height: 48.0,
),
RoundedButton(
title: 'Entrar',
colour: Colors.lightBlueAccent,
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginScreen()));
//Navigator.pushNamed(context, LoginScreen.id);
},
),
RoundedButton(
title: 'Registro',
colour: Colors.blueAccent,
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => RegistrationScreen()));
//Navigator.pushNamed(context, RegistrationScreen.id);
},
),
],
),
),
);
}
}
Run Code Online (Sandbox Code Playgroud)
login_screen.dart:
import 'package:flutter/material.dart';
import 'package:tic_tac_2/components/rounded_button.dart';
import 'package:tic_tac_2/constants.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'home_screen.dart';
import 'package:tic_tac_2/models/user.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:email_validator/email_validator.dart';
final _firestore = Firestore.instance;
class LoginScreen extends StatefulWidget {
static const String id = 'login_screen';
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
bool showSpinner = false;
final _auth = FirebaseAuth.instance;
String email;
String password;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: ModalProgressHUD(
inAsyncCall: showSpinner,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Flexible(
child: Hero(
tag: 'logo',
child: Container(
height: 200.0,
child: Image.asset('images/pin.png'),
),
),
),
SizedBox(
height: 48.0,
),
TextFormField(
validator: (val) => !EmailValidator.validate(val, true)
? 'Correo inválido'
: null,
keyboardType: TextInputType.emailAddress,
textAlign: TextAlign.center,
onChanged: (value) {
email = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Escribe tu correo'),
),
SizedBox(
height: 8.0,
),
TextFormField(
validator: (val) =>
val.length < 6 ? 'La contraseña es muy corta' : null,
obscureText: true,
textAlign: TextAlign.center,
onChanged: (value) {
password = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Escribe tu contraseña'),
),
SizedBox(
height: 24.0,
),
RoundedButton(
title: 'Entrar',
colour: Colors.lightBlueAccent,
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() {
showSpinner = true;
});
try {
final user = await _auth.signInWithEmailAndPassword(
email: email, password: password);
if (user != null) {
return _firestore
.collection('user')
.document(user.user.uid)
.get()
.then((DocumentSnapshot ds) {
User localUser = User(
uid: user.user.uid,
email: email,
role: ds.data['role']);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(
user: user.user,
newUser: localUser,
)));
});
}
setState(() {
showSpinner = false;
});
} catch (e) {
setState(() {
showSpinner = false;
});
Alert(
context: context,
title: "Error en el registro",
desc: e)
.show();
print(e);
}
}
},
),
],
),
),
),
),
);
}
}
Run Code Online (Sandbox Code Playgroud)
home_screen.dart:
import 'package:tic_tac_2/models/user.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:async';
import 'package:tic_tac_2/models/restaurants_data.dart';
import 'package:provider/provider.dart';
import 'package:tic_tac_2/models/promotions_data.dart';
import 'package:tic_tac_2/widgets/RestaurantList.dart';
import 'package:geolocator/geolocator.dart';
Geoflutterfire geo = Geoflutterfire();
FirebaseUser loggedInUser;
User localUser;
class HomeScreen extends StatefulWidget {
final FirebaseUser user;
final User newUser;
const HomeScreen({Key key, this.user, this.newUser}) : super(key: key);
static const String id = 'home_screen';
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _firestore = Firestore.instance;
GoogleMapController mapController;
var pos;
Stream<dynamic> query;
StreamSubscription subscription;
@override
void dispose() {
// TODO: implement dispose
super.dispose();
subscription.cancel();
}
@override
void initState() {
// TODO: implement initState
super.initState();
if (localUser == null) {
localUser = widget.newUser;
loggedInUser = widget.user;
}
}
@override
Widget build(BuildContext context) {
void _getCurrentLocation(BuildContext context) async {
try {
Position position = await Geolocator()
.getCurrentPosition(desiredAccuracy: LocationAccuracy.low);
print('lat');
print(position.latitude);
print('lng');
print(position.longitude);
final QuerySnapshot restaurants = await _firestore.collection('restaurants').getDocuments();
for(var restaurant in restaurants.documents) {
print(restaurant);
Provider.of<RestaurantsData>(context).addRestaurant(
name: restaurant.data['name'],
owner: restaurant.data['owner'],
location: restaurant.data['location'],
uid: restaurant.data['uid'],
);
}
} catch (e) {
print(e);
}
}
WidgetsBinding.instance.addPostFrameCallback((_) => _getCurrentLocation(context));
print(Provider.of<RestaurantsData>(context).restaurants);
return Scaffold(
backgroundColor: Color(0xff000080),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(
top: 60.0,
bottom: 30.0,
left: 30.0,
right: 30.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CircleAvatar(
child: Icon(
Icons.list,
size: 30.0,
color: Color(0xff000080),
),
backgroundColor: Colors.white,
radius: 30.0,
),
SizedBox(
height: 10.0,
),
Text(
'Tic Tac',
style: TextStyle(
fontSize: 50.0,
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
Text(
'Restaurantes',
style: TextStyle(color: Colors.white, fontSize: 18.0),
)
],
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
),
child:
Provider.of<RestaurantsData>(context).restaurants.length > 0
? RestaurantList()
: Container(),
),
),
],
),
);
}
}
Run Code Online (Sandbox Code Playgroud)
据我所知,导致 home_screen 文件出现问题的原因是“ getCurrentLocation(BuildContext context){}”函数,以及我调用它的方式和时间。我试过把所有东西都变成 statelessWidgets,在没有“WidgetsBinding.instance.addPostFrameCallback(( ) => _getCurrentLocation(context));”的情况下调用 getLocation 函数 线。我尝试不将上下文传递给函数,以及我尝试过的其他解决方案。
我真的很感谢你的帮助,我想提前感谢你。如果您对代码有任何疑问,我将非常乐意回答所有问题。
om-*_*-ha 16
请自行或通过我在下面的解释来理解解决方案。不要只是在不理解的情况下使用我的答案。虽然这是一个简单的标志,您可以指定/翻转,但理解它是为什么甚至使用 Provider 的核心。
在您的_getCurrentLocation方法中,假设已更新到最新的Providerpub 版本。改变:
Provider.of<RestaurantsData>(context).addRestaurant();context.watch<RestaurantsData>().addRestaurant();
到
Provider.of<RestaurantsData>(context, listen: false).addRestaurant();context.read<RestaurantsData>().addRestaurant();
平行于与旧verison旧的解决方案绘图,read扮演着相同的角色listen: false。要么用于修复由watch扮演与listen: true. 可以在此处和此处找到对此的重要解释。感谢用户 Vinoth Vino 通过他的评论提醒这一新变化。
在你的_getCurrentLocation方法中,改变
Provider.of<RestaurantsData>(context).addRestaurant()
Run Code Online (Sandbox Code Playgroud)
到
Provider.of<RestaurantsData>(context, listen: false).addRestaurant()
Run Code Online (Sandbox Code Playgroud)
正如错误所示
试图从小部件树外部监听提供者公开的值。
您正在从窗口小部件树外部的 Provider 实例获取通知更新。即您的 Provider 实例正在调用 Provider 方法NotifyListeners(),该方法将更新发送到所有侦听器。你的问题中的这个特殊调用正在监听这些更新,即:Provider.of<RestaurantsData>(context)
发生这种情况是因为addPostFrameCallback导致其参数回调在您的小部件树之外被调用。后一个回调是封装_getCurrentLocation本地函数。反过来,此函数具有 Provider 实例调用。这一系列事件导致提供程序调用侦听小部件树之外的更新。
在小部件树之外收听通知更新是错误的,例如用户操作回调或initState.
要解决此问题,您需要在小部件树之外的代码范围内listen为其非默认值分配标志false。例如initState或用户交互回调或不直接在小部件的构建方法下的任何代码范围。
这就是我使用提供程序的方式:
Consumer一般来说,以及Selector当您有很多 Provider 监听更新出于不同原因而您只想为一个特定的小部件树重建时,出于性能原因对何时导致小部件重建很挑剔/有选择性原因。这些用于侦听更改的方法更加通用:使正在重建的小部件块更加清晰,并且还可以在Provider没有BuildContext例如 fromStatelessWidget或StatefulWidget没有引用的 a 的某些辅助方法的情况下进行访问BuildContext。Provider.of<T>(context, listen: false)Provider.of<T>(context, listen: false).myMethod()egProvider.of<RestaurantsData>(context, listen: false).addRestaurant()因为Provider在这种情况下大多数时候你不需要监听更新。要进一步了解listen标志行为和异常背后的原因,请查看此处的GitHub 文档和源代码文档。如果您真的很感兴趣,请查看此 GitHub 讨论。
| 归档时间: |
|
| 查看次数: |
4786 次 |
| 最近记录: |