我正在使用 Provider
provider: ^4.1.2
我有2页 。A、B页。
A页和B页显示了相同的简单设计,有以下代码。
PageA.dart
class PageA extends StatelessWidget {
var songTitle = '';
@override
Widget build(BuildContext c) {
return Column(children: <Widget>[
FloatingActionButton.extended(
onPressed: () {
// LOGIC : TRANSFER TO PAGE B AFTER CLICKED BUTTON FROM PAGE A
Navigator.push(context, MaterialPageRoute(
builder: (context) => PageB()));
},
label: Text('Click'),
),
),
Text('$songTitle');
// STEP 2 : while page B already got `songTitle`
// to show on UI, I want `songTitle` also update on this page A
// in the same time
]);
}
}
PageB.dart
class PageB extends StatelessWidget {
var songTitle = '';
@override
Widget build(BuildContext c) {
return Text('$songTitle');
}
@override
void initState() {
super.initState();
// LOGIC : CALL API TO GET `songTitle` TO UPDATE ON UI
api.request().then((title) {
setState(() {
this.songTitle = title;
// STEP 1 : this causes update to page B
// show `songTitle` got from api success
});
});
}
}
这些代码在运行时没有错误。
我想要的是在 Step 1
得到 songTitle
数据。
它将更新其数据到A页(Step 2
)和B页(Step 1
)在同一时间内通过使用 Provider
(前。Provider.of(context)
...)
知道的人。
请告诉我。
谢谢你了
ps:我不想使用这些方法。
- 使用 callback
作为参数,从A页发送至B页。
- 那么,就叫 callback()
后得 songTitle
甚至叫 Navigator.of(context).pop(songTitle)
在B页。
目前,我知道这些种类的 Provider
喜欢: ChangeNotifierProvider
, StreamProvider
,.v.v.
成功地用于包裹一个 StatelessWidget
(在 build()
方法),通过这些组件 。
- ChangeNotifierProvider
- Model extends ChangeNotifier
与 notifyListener()
包括
- Provider<Model>.of(context)
或 Consumer<Model>()
ps : 完整的示例代码。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [ChangeNotifierProvider<Song>(create: (context) => Song())],
child: MaterialApp(home: PageA()),
);
}
}
class PageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer<Song>(builder: (context, song, child) {
return Column(
children: <Widget>[
// SONG TITLE
song != null ? Text(song.songTitle) : Text(''),
// Button
MaterialButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => PageB()),
),
child: Text('Button'),
),
],
mainAxisAlignment: MainAxisAlignment.center,
);
}),
),
);
}
}
class PageB extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return PageBState();
}
}
class PageBState extends State<PageB> {
@override
Widget build(BuildContext c) {
final song = Provider.of<Song>(c, listen: false);
song.updateSongTitle('New Title');
return Container(
child: Text('Page B'),
color: Colors.white,
);
}
}
class Song extends ChangeNotifier {
String songTitle = 'Title';
void updateSongTitle(String newTitle) {
songTitle = newTitle;
notifyListeners();
}
}
当前的错误输出是 :
I/flutter (12496): ══╡ EXCEPTION CAUGHT BY
FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
I/flutter (12496): The following assertion was thrown while dispatching notifications for Song:
I/flutter (12496): setState() or markNeedsBuild() called during build.
I/flutter (12496): This _InheritedProviderScope<Song> widget cannot be marked as needing to build because the framework
I/flutter (12496): is already in the process of building widgets. A widget can be marked as needing to be built during
I/flutter (12496): the build phase only if one of its ancestors is currently building. This exception is allowed
I/flutter (12496): because the framework builds parent widgets before children, which means a dirty descendant will
I/flutter (12496): always be built. Otherwise, the framework might not visit this widget during this build phase.
I/flutter (12496): The widget on which setState() or markNeedsBuild() was called was:
I/flutter (12496): _InheritedProviderScope<Song>
I/flutter (12496): The widget which was currently being built when the offending call was made was:
I/flutter (12496): PageB
I/flutter (12496):
I/flutter (12496): When the exception was thrown, this was the stack:
I/flutter (12496): #0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4211:11)
...
I/flutter (12496): (elided 3 frames from dart:async)
I/flutter (12496):
I/flutter (12496): The Song sending notification was:
I/flutter (12496): Instance of 'Song'
I/flutter (12496): ════════════════════════════════════════════════════════════════════════════════════════════════════
你读过Provider的例子吗?
你可以做这样的事情。
class Song{
String title;
Song(this.title);
}
class MyProvider extends ChangeNotifier{
Song song;
void getSongInformation(String song) async{
try{
final req = await doYourAPIRequest();
song = Song(req['title']);
notifyListeners(); // Notify the listeners about this change
}catch(e){
// handle the error
}
}
}
然后在你的小工具里
Consumer<Song>(
builder: (context, song, child) {
return Text(song.title);
},
)
因此,任何正在监听歌曲变化的Widgets都会在你调用时收到任何变化的通知。notifyListeners()
如果你只需要Provider来通知一个单一的Song变化,那么你也可以使用ValueNotifierProvider。另外,把你的Consumer放在Widget树的最深处,以便只重建依赖于该值的Widget,这是一个很好的做法。
如果我没有理解错的话,你是想使用Provider包来管理你的状态,并将数据传递到你的Widget树中。
你需要使用ChangeNotifier和ChangeNotifierProvider来实现这个目标。
创建一个单独的类,像这样。
class Song extends ChangeNotifier {
/// Here create a string for the songTitle you want to pass around
/// You can also set a default value here if there are any
String songTitle;
/// Then create a method that updates the songTitle from any widget that depends on this class
void updateSongTitle(String newTitle) {
songTitle = newTitle;
notifyListeners();
}
}
下一步是用ChangeNotifierProvider来包装你的MaterialApp,或者更好的是用MultiProvider来包装MaterialApp(这是在你需要用不同的provider来提供不同的widget的情况下),比如下面的main.dart。
return MultiProvider(
provider: [
/// Your ChangeNotifierProvider goes here like so
ChangeNotifierProvider(
create: (context) => Song(), /// This is the class that was created, don't forget to import it
),
],
child: MaterialApp(
/// Here should be familiar to you
),
)
对于B页: / 从你的代码中
class PageB extends StatefulWidget{ /// Remember StatelessWidget can't have initState
@override
void initState() {
super.initState();
// LOGIC : CALL API TO GET `songTitle` TO UPDATE ON UI
api.request().then((title) {
final song = Provider.of<Song>(context, listen: false);
song.updateSongTitle(title); /// From the title String you get from your api call
});
}
@override
Widget build(BuildContext c) {
/// Initialise another Provider for your Song class like so:
/// A Consumer can also be used here, but I opted to use Provider.of(context)
final song = Provider.of<Song>(context);
return Text(song.songTitle);
}
}
然后,对于你的页面A,从你的代码示例中可以看出
class PageA extends StatelessWidget {
@override
Widget build(BuildContext c) {
return Column(children: <Widget>[
FloatingActionButton.extended(
onPressed: () {
// LOGIC : TRANSFER TO PAGE B AFTER CLICKED BUTTON FROM PAGE A
Navigator.push(context, MaterialPageRoute(
builder: (context) => PageB()));
},
label: Text('Click'),
),
),
/// The text widget here gets its field via the Provider and its value is what was receive from Page B when where the api call was made to get the title
Consumer<Song>(
builder: (context, song, child) => Text(
song.songTitle,
),
), /// Any other widget you have can continue from here
]);
}
}
希望能成功!