Flutter List View Pagination lazy load the images from firebase - flutter

i try to implement pagination on my image list from firebase, and i need some help..
// Fetch images from databse for list
initSliderImages() async {
var result = await FirebaseFirestore.instance.collection('galerytab1');
result.snapshots().listen((data) {
List imgs = [];
data.docChanges.forEach((change) {
var imageData = change.doc.data();
String image = imageData?['url'];
imgs.add(CachedNetworkImageProvider(image));
});
setState(() {
images = imgs;
});
});}
I create ListView.build and try to fetch data using
_scrollListener() {
print(controller.position.extentAfter);
if (controller.position.extentAfter < 13) {
setState(() {
images.addAll(List.generate(2, (index) => 'Inserted $index'));
});
}_scrollListener() {
print(controller.position.extentAfter);
if (controller.position.extentAfter < 13) {
setState(() {
images.addAll(List.generate(2, (index) => 'Inserted $index'));
});
}}
#override
void
dispose() {
controller.removeListener(_scrollListener);
super.dispose();}
but i can't leazy load images that are storaged in this database collection 'galerytab1'
Question: How can i leazy load images from this collection'galerytab1' and display them in listview
Thank You!

setState will re-render your build method which causes the list and elements in the list to re-render as well. You can use KeyValue to prevent this from happening.
Also with modern flutter you can use NotificationListener instead of a ScrollController
This sample should give you a rough idea of how it works.
you can run it on dartpart
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: PaginationExample(
key: ValueKey('pagination'),
),
),
),
);
}
}
class PaginationExample extends StatefulWidget {
const PaginationExample({super.key});
#override
State<PaginationExample> createState() => PaginationExampleState();
}
class PaginationExampleState extends State<PaginationExample> {
List<String> urls = [];
#override
void initState() {
super.initState();
addUrls();
}
#override
void dispose() {
super.dispose();
}
void addUrls() {
final List<String> newUrls = List.generate(
10,
(_) {
int random = Random().nextInt(500) + 250; // 250-500
return 'https://picsum.photos/$random/$random';
},
);
setState(() {
urls.addAll(newUrls);
});
}
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
if (notification.metrics.pixels ==
notification.metrics.maxScrollExtent) {
addUrls();
}
return true;
},
child: ListView.builder(
key: widget.key,
itemCount: urls.length,
itemExtent: 250,
itemBuilder: (context, index) {
return Image.network(
urls[index],
key: ValueKey(urls[index]),
);
},
),
);
}
}

Related

How to fix abscence of fetching data inside initState in Flutter?

I am building an image with list of countries
And get data of countries from Api, I call a function inside initState()
And data is not showed.
But if I try to type and then delete text inside textFormField then data appears
This is my widget:
class SignUp extends StatefulWidget {
const SignUp({super.key});
#override
State<SignUp> createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
List _allCountries = [];
List _foundCountries = [];
bool showDropDown = false;
final ApiService _apiService = ApiService();
Future<void> getCountries() async {
try {
final response = await _apiService.getCountries();
_allCountries = response['list'];
log(_allCountries.toString());
} catch (e) {
log(e.toString());
rethrow;
}
}
#override
void initState() {
getCountries();
_foundCountries = _allCountries;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextFormField(
onTap: () {
setState(() {
showDropDown = !showDropDown;
// _foundCountries = _allCountries; If I try to add this code, data appears when screen is loaded
});
},
),
SizedBox(
child: showDropDown ? Column(
children: [
Flexible(
child: ListView.builder(
itemCount: _foundCountries.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
_foundCountries[index]['name']
),
);
},
)
)
],
) : null,
)
],
),
);
}
}
The response from Api looks like this:
{"success":true,"list":[{"id": 1, "name": "Abkhazia"}, {"id":2, "name": ""Afghanistan"}]}
I think, the problem is inside initState(), when I set _filteredCountries = _allCountries
I don't know why flutter can't set data inside state. How can I fix that?
Try setState after you get response like this:
Future<void> getCountries() async {
try {
final response = await _apiService.getCountries();
_allCountries = response['list'];
setState(() {
_foundCountries = _allCountries;
});
} catch (e) {
log(e.toString());
rethrow;
}
}
and remove _foundCountries = _allCountries; line inside your initState, when you call this line, _allCountries still don't have any data

How to send the fetched data everytime to some other widget in Flutter

Wanted to pass the updated values of fetchedEntriesInApp to PasswdList widget everytime it loads.
Below is my code.
main.dart
Future fetchEntries() async {
var fetchedEntries = [];
var db = FirebaseFirestore.instance;
final res = await db.collection("password_entries").get().then((event) {
for (var doc in event.docs) {
var resDic = {
"entry_id": doc.id,
"data": doc.data(),
};
fetchedEntries.add(resDic);
}
});
return fetchedEntries;
}
class Body extends StatefulWidget {
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
late Future fetchedEntriesInApp;
#override
void initState() {
super.initState();
fetchedEntriesInApp = fetchEntries();
}
void refreshEntries() {
setState(() {
fetchedEntriesInApp = fetchEntries();
});
}
#override
Widget build(BuildContext context) {
setState(() {});
return FutureBuilder(
future: fetchedEntriesInApp!,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading');
}
return Column(children: [
PasswdList(fetchedEntriesInApp),
RaisedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/addPasswd',
arguments: AddPasswd(fetchEntries),
);
},
child: Text('Add Psswd'),
),
]);
});
}
}
PasswdList Widget
class PasswdList extends StatefulWidget {
var abc;
PasswdList(this.abc);
#override
State<PasswdList> createState() => _PasswdListState();
}
class _PasswdListState extends State<PasswdList> {
var fetchedEntriesInApp;
#override
Widget build(BuildContext context) {
var entries;
setState(() {
entries = widget.abc;
});
print(entries);
return Container(
height: 500,
child: ListView(
children: [
PasswdCard(),
],
),
);
}
}
You can add one variable for password list in your password list widget like,
class PasswdList extends StatefulWidget {
var passwordlist;
PasswdList(this.passwordlist);
#override
State<PasswdList> createState() => _PasswdListState();
}
class _PasswdListState extends State<PasswdList> {
var fetchedEntriesInApp;
#override
Widget build(BuildContext context) {
var entries;
setState(() {
entries = widget.passwordlist;
});
print(entries);
return Container(
height: 500,
child: ListView(
children: [
PasswdCard(),
],
),
);
}
}
And you can pass it to the navigator like,
RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>PasswdList (fetchedEntriesInApp.values,
),
);
},
Since your PasswdList is a Stateful widget and it is embedded in your view, you can use the callback
#override
void didUpdateWidget(covariant PasswdList oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.abc != oldWidget.abc)
setState(() {
//You can have a var in your state class and re-assign it to the new value
});
}
Note: in order for this to work, you need to re-initialize the abc list and pass it to your widget, otherwise you might need to change the if statement condition

Flutter pull down to refresh seems to work but it does not refresh

I am a beginner in Flutter and I'm struggling with this problem for quite some time now.
I have made a Flutter app which fetches posts from a wordpress website. Everything works fine, except the pull to refresh. It seems to work (at least the indicator is working), but it doesn't update in the background and I have no clue where the problem could be.
Please take a look to my my posts_list.dart file:
import 'package:wordpress_flutter/widgets/refresh_widget.dart';
import '../model/post_entity.dart';
import '../network/wp_api.dart';
import '../widgets/post_list_item.dart';
import '../widgets/refresh_widget.dart';
class PostsList extends StatefulWidget {
int category = 0;
PostsList({this.category = 0});
#override
_PostsListState createState() => _PostsListState();
}
class _PostsListState extends State<PostsList> {
final keyRefresh = GlobalKey<RefreshIndicatorState>();
List<PostEntity> posts = [];
int page = 0;
ScrollController _scrollController = new ScrollController();
bool isLoading = false;
#override
void initState() {
super.initState();
getData();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
getData();
}
});
}
Future getData() async {
keyRefresh.currentState?.show();
if (!isLoading) {
setState(() {
page++;
isLoading = true;
});
WpApi.getPostsList(category: widget.category, page: page).then((_posts) {
setState(() {
isLoading = false;
posts.addAll(_posts);
});
});
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => SizedBox(
height: 300,
child: buildList(),
);
Widget buildList() => posts.isEmpty
? Center(child: CircularProgressIndicator())
: RefreshWidget(
keyRefresh: keyRefresh,
onRefresh: getData,
child: ListView.builder(
itemCount: posts.length + 1,
shrinkWrap: true,
primary: false,
controller: _scrollController,
itemBuilder: (context, index) {
if (index == posts.length) {
return _buildProgressIndicator();
} else {
return PostListItem(posts[index]);
}
}));
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Visibility(
visible: isLoading,
child: CircularProgressIndicator(
color: Colors.blue,
),
),
),
);
}
}
Here's the refresh_widget.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class RefreshWidget extends StatefulWidget {
final GlobalKey<RefreshIndicatorState> keyRefresh;
final Widget child;
final Future Function() onRefresh;
const RefreshWidget({
Key key,
this.keyRefresh,
#required this.onRefresh,
#required this.child,
}) : super(key: key);
#override
_RefreshWidgetState createState() => _RefreshWidgetState();
}
class _RefreshWidgetState extends State<RefreshWidget> {
#override
Widget build(BuildContext context) =>
Platform.isAndroid ? buildAndroidList() : buildIOSList();
Widget buildAndroidList() => RefreshIndicator(
key: widget.keyRefresh,
onRefresh: widget.onRefresh,
child: widget.child,
);
Widget buildIOSList() => CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
CupertinoSliverRefreshControl(onRefresh: widget.onRefresh),
SliverToBoxAdapter(child: widget.child),
],
);
}
You should definitely check out the package called pull_to_refresh. It helps out a lot.
From my observation, your issue could be that you add all your posts that you get to the posts list. Instead, try replacing the list by the incoming list. Your way would only work for the first time, since initially the posts list is empty. However, the next time, it just adds another list of posts to all the posts you got from initState.
Future getData() async {
keyRefresh.currentState?.show();
if (!isLoading) {
setState(() {
page++;
isLoading = true;
});
WpApi.getPostsList(category: widget.category, page: page).then((_posts) {
setState(() {
isLoading = false;
posts = _posts
});
});
}
}

how to make this lazyload scrolling working with provider

it take about 7 days trying make a working example for lazyload listview with provider in flutter with real world example and it's still not working because i think something is missing
As a note : the first load , works good and when i scroll it's print (scroll) but nothing happened it's still in the same page
if i try to return _todolist variable in the _onScrollUpdated it not change page correctly and after three times i see this error
E/flutter ( 7713): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)]
Unhandled Exception: type 'String' is not a subtype of type
'List' E/flutter ( 7713): #0 TodoService.fetchTodos
(package:flutter_todo_provider/services/todo_service.dart:32:21)
json example
https://jsonformatter.org/52c83e
todos_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_todo_provider/helpers/http_exception.dart';
import 'package:provider/provider.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/services/todo_service.dart';
class TodosScreen extends StatefulWidget {
#override
_TodosScreenState createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
ScrollController _controller;
List<dynamic> _todoList;
bool _isLoading ;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Configuration.AppName),
),
body: FutureBuilder(
future: _fetchListItems(),
builder: (context, snapshot){
if(snapshot.hasData){
return _listItems(snapshot.data);
}
return _buildProgressIndicator();
}
),
);
}
_fetchListItems() async {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
return _todoList ;
}
Widget _listItems(data){
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return ListView.builder(
controller: _controller,
itemCount: data.length ,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle:Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Future<void> _onScrollUpdated() async {
print("Scroll11");
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll ) {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
// return _todoList ; if use this line i see the error
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
}
}
Widget _buildProgressIndicator() {
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: _isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
}
todo_service.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/models/todo.dart';
class TodoService with ChangeNotifier {
bool isLoading = false;
bool isFetching = false;
int currentPage = 1;
int totalRows = 10;
List<Todo> items = [];
loadNextPage() async {
await fetchTodos(currentPage);
currentPage++;
notifyListeners();
}
Future fetchTodos(int currentPage) async {
try {
//404
var options = Options(headers: {
HttpHeaders.authorizationHeader: 'Basic ${Configuration.authToken}'
});
Map<String, dynamic> qParams = {
'current_page': currentPage,
};
Response response = await Dio().get('${Configuration.ApiUrl}/todos/my_todos', options: options, queryParameters: qParams);
List<dynamic> responseBode = response.data["data"];
responseBode.forEach(( dynamic json) {
items.add(Todo.fromJson(json));
});
notifyListeners();
} on DioError catch (e) {
print("Error Message" + e.response.statusMessage);
return items=[];
}
}
}
Here is the code:
class TodoScreen extends StatefulWidget {
// Your service goes here
// (the class extending ChangeNotifier)
#override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final TodoService todoService = TodoService();
ScrollController _controller;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
loadNextPage();
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Configuration.AppName'),
),
body: ChangeNotifierProvider.value(
value: todoService,
child: Consumer<TodoService>(builder: (_, ctl, __) {
if (todoService.isLoading) {
return _buildProgressIndicator();
} else {
return _listItems(todoService.items);
}
}),
),
);
}
Widget _listItems(data) {
return ListView.builder(
controller: _controller,
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle: Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: CircularProgressIndicator(),
),
);
}
Future<void> _onScrollUpdated() async {
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll) {
todoService.loadNextPage();
}
}
}
Note that i didn't make changes to your service. The notifyListeners will do all the job for us.
When you are using Provider, the idea is to keep all your data inside the controller or service (the class that extends ChangeNitifier) and just use the variables with notifyListeners to change the behavior of your screen.
The screen needs to be listening for changes, for this we use the pair ChangeNotifierProvider.value with Consumer(builder: (_, ctl, __) {}).
Use ChangeNotifierProvider in some upper level of the widget tree and use Consumer only where you need the widget to be updated. You can even use more than one Consumer, they all just need to be under ChangeNotifierProvider.

How to use flutter provider in a statefulWidget?

I am using flutter_provider for state management. I want to load some items on page(statefulwidget) load from Api. I am showing a loader on page start and want to show the items once they are fetched.
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,
),
);
}
}
My provider is like this -
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;
}
}
This gives an error of -
inheritFromWidgetOfExactType(_Provider<VideosProvider>) or inheritFromElement() was called before _PlaylistState.initState() completed.
here is the build method of the parent of playList class, wrapped in a changenotifier,
Widget build(BuildContext context) {
return ChangeNotifierProvider<VideosProvider>(
builder: (BuildContext context) => VideosProvider(),
child: MaterialApp(
title: "My App",
home: new Playlist(),
),
);
}
So, all the examples on flutter_provider on internet show usage of provider on a statelesswidget, where state changes occur on user interactions like a button click. None about how to use provider in a statefulWidget, and cases where data has to be updated on page load without any interaction.
I am aware of streambuilder and futurebuilder for this kind of scenarios, but want to understand how this can be done with flutter_provider. How can I use provider to call fetchVideosList in initState(on pageload)? Does this case can/should be handled with a statelessWidget?
Does this case can/should be handled with a statelessWidget?
The answer is : No, it does not
I am heavy user of StatefulWidget + Provider. I always use this pattern for displaying a Form which contains fields, that available for future edit or input.
Updated : February 9 2020
Regarding to Maks comment, I shared better way to manage provider using didChangeDependencies.
You may check to this github repository
main.dart
First Step
Initiate PlayListScreen inside ChangeNotifierProvider
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();
},
),
);
},
),
),
);
}
}
Second Step
Make PlaylistScreen as Stateful Widget to hold TextEditingContoller
and other values.
playlistScreen.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']}"),
);
},
);
}
}
Last Step
make provider to handle Business Logic
videosProvider.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);
}
}
Old answer : Aug 19 2019
In my case :
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 as a class, need to update their latest value from API. So, initially, it will request to some URL and updates corresponding values.
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
}
In your case :
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
}
}
When using Provider for state management you don't need to use StatefullWidget, so how can you call a method of the ChangeNotifier on start of the app?
You can simply do that in the constructor of the ChangeNotifier, so that when you point out VideosProvider() to the ChangeNotifierProvider Builder the constructor will get called the first time the provider constructs the VideosProvider, so:
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;
}
}
When using a Provider you don’t need to use a StatefulWidget (as of a tutorial by the Flutter team State management
You may use the following tutorial to see how to fetch data with a provider and a
StatelessWidget: Flutter StateManagement with Provider