这种情况可以/应该用 statelessWidget 处理吗?
答案是:不,它没有
我是 StatefulWidget + Provider 的重度用户。我总是使用这种模式来显示一个包含字段的表单,这些字段可用于将来的编辑或输入。
更新日期:2020 年 2 月 9 日
关于 Maks 的评论,我分享了使用 didChangeDependencies 管理提供者的更好方法。
您可以查看此github 存储库
主要.dart
- 第一步
在 ChangeNotifierProvider 中启动 PlayListScreen
class PlaylistScreenProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<VideosProvider>(
create: (_) {
return VideosProvider();
},
child: PlaylistScreen(),
);
}
}
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Screen'),
),
body: Center(
child: RaisedButton(
child: Text("Go To StatefulWidget Screen"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) {
return PlaylistScreenProvider();
},
),
);
},
),
),
);
}
}
- 第二步
将 PlaylistScreen 设为 Stateful Widget 以保存 TextEditingContoller 和其他值。
播放列表屏幕.dart
class PlaylistScreen extends StatefulWidget {
@override
_PlaylistScreenState createState() => _PlaylistScreenState();
}
class _PlaylistScreenState extends State<PlaylistScreen> {
List _playlistList;
String _errorMessage;
Stage _stage;
final _searchTextCtrl = TextEditingController();
@override
void dispose() {
super.dispose();
_searchTextCtrl.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final videosState = Provider.of<VideosProvider>(context);
_playlistList = videosState.playlist;
_stage = videosState.stage;
_errorMessage = videosState.errorMessage;
}
void actionSearch() {
String text = _searchTextCtrl.value.text;
Provider.of<VideosProvider>(context, listen: false)
.updateCurrentVideoId(text);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: <Widget>[
Container(
child: RaisedButton.icon(
icon: Icon(Icons.search),
label: Text("Filter"),
onPressed: () {
actionSearch();
},
),
),
Container(
child: TextField(
controller: _searchTextCtrl,
onSubmitted: (value) {
actionSearch();
},
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Please input 1 or 2',
),
),
),
Flexible(
child: _stage == Stage.DONE
? PlaylistTree(_playlistList)
: _stage == Stage.ERROR
? Center(child: Text("$_errorMessage"))
: Center(
child: CircularProgressIndicator(),
),
)
],
),
),
);
}
}
class PlaylistTree extends StatelessWidget {
PlaylistTree(this.playlistList);
final List playlistList;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: playlistList.length,
itemBuilder: (context, index) {
var data = playlistList[index];
return Container(
child: Text("${data['id']} - ${data['first_name']}"),
);
},
);
}
}
- 最后一步
让提供者处理业务逻辑
视频提供者.dart
enum Stage { ERROR, LOADING, DONE }
class VideosProvider with ChangeNotifier {
String errorMessage = "Network Error";
Stage stage;
List _playlist;
int _currentVideoId;
VideosProvider() {
this.stage = Stage.LOADING;
initScreen();
}
void initScreen() async {
try {
await fetchVideosList();
stage = Stage.DONE;
} catch (e) {
stage = Stage.ERROR;
}
notifyListeners();
}
List get playlist => _playlist;
void setPlayList(videosList) {
_playlist = videosList;
}
void validateInput(String valueText) {
if (valueText == ""){
this._currentVideoId = null;
return;
}
try {
int valueInt = int.parse(valueText);
if (valueInt == 1 || valueInt == 2){
this._currentVideoId = valueInt;
}
else {
this.errorMessage = "Use only 1 and 2";
throw 1;
}
} on FormatException catch (e) {
this.errorMessage = "Must be a number";
throw 1;
}
}
void updateCurrentVideoId(String value) async {
this.stage = Stage.LOADING;
notifyListeners();
try {
validateInput(value);
await fetchVideosList();
stage = Stage.DONE;
} on SocketException catch (e) {
this.errorMessage = "Network Error";
stage = Stage.ERROR;
} catch (e) {
stage = Stage.ERROR;
}
notifyListeners();
}
Future fetchVideosList() async {
String url;
if (_currentVideoId != null) {
url = "https://reqres.in/api/users?page=$_currentVideoId";
} else {
url = "https://reqres.in/api/users";
}
http.Response response = await http.get(url);
var videosList = json.decode(response.body)["data"];
setPlayList(videosList);
}
}
旧答案:2019 年 8 月 19 日
就我而言:
form_screen.dart
class Form extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<FormProvider>(
builder: (_) {
return FormProvider(id: ...); // Passing Provider to child widget
},
child: FormWidget(), // So Provider.of<FormProvider>(context) can be read here
);
}
}
class FormWidget extends StatefulWidget {
@override
_FormWidgetState createState() => _FormWidgetState();
}
class _FormWidgetState extends State<FormWidget> {
final _formKey = GlobalKey<FormState>();
// No need to override initState like your code
@override
Widget build(BuildContext context) {
var formState = Provider.of<FormProvider>(context) // access any provided data
return Form(
key: _formKey,
child: ....
);
}
}
FormProvider 作为一个类,需要从 API 更新它们的最新值。因此,最初,它会请求某个 URL 并更新相应的值。
form_provider.dart
class FormProvider with ChangeNotifier {
DocumentModel document;
int id;
FormProvider({@required int id}) {
this.id = id;
initFormFields(); // will perform network request
}
void initFormFields() async {
Map results = initializeDataFromApi(id: id);
try {
document = DocumentModel.fromJson(results['data']);
} catch (e) {
// Handle Exceptions
}
notifyListeners(); // triggers FormWidget to re-execute build method for second time
}
在你的情况下:
播放列表.dart
class PlaylistScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<VideosProvider>(
builder: (_) {
return VideosProvider(); // execute construct method and fetchVideosList asynchronously
},
child: Playlist(),
);
}
}
class Playlist extends StatefulWidget {
@override
_PlaylistState createState() => _PlaylistState();
}
class _PlaylistState extends State<Playlist> {
final _formKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
// We *moved* this to build method
// videosState = Provider.of<VideosProvider>(context);
// We *moved* this to constructor method in provider
// videosState.fetchVideosList();
}
@override
Widget build(BuildContext context) {
// Moved from initState
var videosState = Provider.of<VideosProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: RefreshIndicator(
}
}
提供者.dart
class VideosProvider with ChangeNotifier {
VideosProvider() {
// *moved* from Playlist.initState()
fetchVideosList(); // will perform network request
}
List _playlist;
int _currentVideoId;
get playlist => _playlist;
void setPlayList(videosList) {
_playlist = videosList;
}
Future fetchVideosList() async {
http.Response response =
await http.get("http://192.168.1.22:3000/videos-list/");
print(json.decode(response.body));
videos = json.decode(response.body)["data"];
setPlayList(videos);
// return videos; // no need to return
// We need to notify Playlist widget to rebuild itself for second time
notifyListeners(); // mandatory
}
}