Как использовать провайдера флаттера в StatefulWidget?
Я использую flutter_provider для управления состоянием. Я хочу загрузить некоторые элементы при загрузке страницы (statefulwidget) из Api. Я показываю загрузчик в начале страницы и хочу показать элементы после их получения.
PlayList.dart -
class Playlist extends StatefulWidget {
@override
_PlaylistState createState() => _PlaylistState();
}
class _PlaylistState extends State<Playlist> {
var videosState;
@override
void initState() {
super.initState();
videosState = Provider.of<VideosProvider>(context);
videosState.fetchVideosList();
}
@override
Widget build(BuildContext context) {
var videos = videosState.playlist;
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: RefreshIndicator(
child: Container(
width: double.infinity,
height: double.infinity,
child: videos.length
? ListView.builder(
itemBuilder: (BuildContext context, index) {
return _videoListItem(context, index, videos, videosState);
},
itemCount: videos.length,
)
: Center(
child: CircularProgressIndicator(),
),
),
onRefresh: () => null,
),
);
}
}
Мой провайдер такой -
class VideosProvider with ChangeNotifier {
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;
}
}
Это дает ошибку -
inheritFromWidgetOfExactType(_Provider<VideosProvider>) or inheritFromElement() was called before _PlaylistState.initState() completed.
Вот метод сборки родительского класса PlayList, обернутый в changenotifier,
Widget build(BuildContext context) {
return ChangeNotifierProvider<VideosProvider>(
builder: (BuildContext context) => VideosProvider(),
child: MaterialApp(
title: "My App",
home: new Playlist(),
),
);
}
Итак, все примеры на flutter_provider в интернете показывают использование провайдера в виджете без состояний, где изменения состояния происходят при взаимодействии пользователя, например при нажатии кнопки. Ни о том, как использовать провайдера в statefulWidget, и о случаях, когда данные должны обновляться при загрузке страницы без какого-либо взаимодействия.
Я знаю, что такое Streambuilder и futurebuilder для таких сценариев, но я хочу понять, как это можно сделать с помощью flutter_provider. Как я могу использовать провайдера для вызова fetchVideosList в initState(при загрузке страницы)? Может ли этот случай / должен быть обработан с помощью statelessWidget?
3 ответа
Может ли этот случай / должен быть обработан с помощью statelessWidget?
Ответ: нет, это не
Я большой пользователь StatefulWidget + Provider. Я всегда использую этот шаблон для отображения формы, которая содержит поля, доступные для будущего редактирования или ввода.
В моем случае:
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
}
В твоем случае:
PlayList.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(
}
}
provider.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
}
}
При использовании провайдера для управления состоянием вам не нужно использовать StatefullWidget, так как вы можете вызвать метод ChangeNotifier при запуске приложения?
Вы можете просто сделать это в конструкторе ChangeNotifier, чтобы при указании VideosProvider() на Builder ChangeNotifierProvider конструктор вызывался при первом создании провайдера VideosProvider, поэтому:
PlayList.dart:
class Playlist extends StatelessWidget {
@override
Widget build(BuildContext context) {
final videosState = Provider.of<VideosProvider>(context);
var videos = videosState.playlist;
return Scaffold(
appBar: AppBar(
title: Text('My Videos'),
),
body: RefreshIndicator(
child: Container(
width: double.infinity,
height: double.infinity,
child: videos.length
? ListView.builder(
itemBuilder: (BuildContext context, index) {
return _videoListItem(context, index, videos, videosState);
},
itemCount: videos.length,
)
: Center(
child: CircularProgressIndicator(),
),
),
onRefresh: () => null,
),
);
}}
VideosProvider.dart:
class VideosProvider with ChangeNotifier {
VideosProvider(){
fetchVideosList();
}
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;
}
}
При использовании провайдера вам не нужно использовать StatefulWidget (как в руководстве по управлению состоянием команды Flutter)
Вы можете использовать следующий учебник, чтобы увидеть, как получить данные с помощью провайдера и StatelessWidget: Flutter StateManagement with Provider