how to make this lazyload scrolling working with provider - flutter

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.

Related

can't see circularprogressindicator while getting data from api in flutter

I am trying to show data from api and while loading data , there should be shown a circularprogressindicator,
but when I start app..it directly showing data instead of circularprogressindicator
class _HomeScreenState extends State<HomeScreen> {
bool isloading = false;
var maplist ;
Future<void> fetchdata() async {
setState(() {
isloading=true;
});
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return isloading==true ? Center(child: CircularProgressIndicator()) : ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
It's actually working perfectly fine, it shows too fast because it is receiving data quickly(+ could be cache case).
If you like to have more delay you can add, future.delay which is unnecessary
Future<void> fetchdata() async {
setState(() {
isloading = true;
});
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
// get more delay
await Future.delayed(Duration(seconds: 2));
setState(() {
isloading = false;
});
}
A better of way of handling future method with FutureBuilder
Try the following code:
class _HomeScreenState extends State<HomeScreen> {
var maplist;
Future<void> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
setState(() {
maplist = json.decode(resp.body);
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return FutureBuilder(
future: fetchdata(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
return ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
}
You need to use FutureBuilder, it is not good to use async function in initState, try this:
FutureBuilder<List<Map<String,dynamic>>>(
future: fetchdata(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<Map<String,dynamic>> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Text(data[index]['title']));
});
}
}
},
),
also you need to change your fetchdata to this:
Future<List<Map<String,dynamic>>> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
return json.decode(resp.body);
}
Try this one,set isloading default true
class _HomeScreenState extends State<HomeScreen> {
bool isloading = true;
var maplist ;
Future<void> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
));
}
MyBody() {
return isloading ? Center(child: CircularProgressIndicator()) : ListView.builder(
itemCount: maplist.length,
itemBuilder: (context,index){
return Container(
padding: EdgeInsets.all(8.0),
child: Text(maplist[index]['title']));
});
}
}
You can use like that
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool isloading = false;
var maplist;
Future<void> fetchdata() async {
setState(() {
isloading = true;
});
var resp = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
maplist = json.decode(resp.body);
setState(() {
isloading = false;
});
}
#override
void initState() {
super.initState();
fetchdata();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: isloading ? const CircularProgressIndicator() : const MyBody(),
);
}
}
class MyBody extends StatelessWidget {
const MyBody({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
//Write your code here
);
}
}

How can I refresh listview.builder in flutter?

how can I add refresh to listview.builder in flutter?
This is my all code from posts screen when I delete post or add new post not realtime just after hot restart it worked I want to add refresh to listview I called getPosts method in refreshIndecator but not worked please any one can help me? Thanks.
Code :
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:data_connection_checker/data_connection_checker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:social_media_app/models/post.dart';
import 'package:social_media_app/posts/create_post.dart';
import 'package:social_media_app/utils/firebase.dart';
import 'package:social_media_app/widgets/indicators.dart';
import 'package:social_media_app/widgets/posts_view.dart';
class Timeline extends StatefulWidget {
#override
_TimelineState createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
List<DocumentSnapshot> post = [];
bool isLoading = false;
DocumentSnapshot lastDocument;
ScrollController _scrollController;
getPosts() async {
if (isLoading) {
return CircularProgressIndicator();
}
if (mounted)
setState(() {
isLoading = true;
});
QuerySnapshot querySnapshot;
if (lastDocument == null) {
querySnapshot =
await postRef.orderBy('timestamp', descending: true).get();
} else {
querySnapshot = await postRef
.orderBy('timestamp', descending: true)
.startAfterDocument(lastDocument)
.get();
}
lastDocument = querySnapshot.docs[querySnapshot.docs.length - 1];
post.addAll(querySnapshot.docs);
if (mounted)
setState(() {
isLoading = false;
});
}
#override
void initState() {
super.initState();
getPosts();
_scrollController?.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.25;
if (maxScroll - currentScroll <= delta) {
getPosts();
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Admin App',
style: TextStyle(fontWeight: FontWeight.w900),
),
leading: IconButton(
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (_) => CreatePost())),
icon: Icon(Feather.plus)),
centerTitle: true,
),
body: isLoading
? circularProgress(context)
: ListView.builder(
controller: _scrollController,
itemCount: post.length,
itemBuilder: (context, index) {
internetChecker(context);
PostModel posts = PostModel.fromJson(post[index].data());
return Padding(
padding: const EdgeInsets.all(10.0),
child: Posts(post: posts),
);
},
),
);
}
internetChecker(context) async {
bool result = await DataConnectionChecker().hasConnection;
if (result == false) {
showInSnackBar('No Internet Connection', context);
}
}
void showInSnackBar(String value, context) {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(value)));
}
}
Why don't you try using a Refresh Indicator Widget for this purpose. Check sample usage in the Flutter official documentation

Looking up a deactivated widget's ancestor is unsafe. How do i Save a reference?

To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
How do i save a reference, i don't have any idea, i am new to flutter.
This is my sample code:Images are in order
First
second
third
class QuestionFeed extends StatefulWidget {
final FirebaseUser loggedInUser;
QuestionFeed({ this.loggedInUser });
#override
_QuestionFeedState createState() => _QuestionFeedState();
}
class _QuestionFeedState extends State<QuestionFeed> {
List<Widget> questions;
List<String> followingsList=[];
bool x;
final _scaffoldKey=GlobalKey<ScaffoldState>();
retrieveTimeLine()async{
QuerySnapshot querySnapshot =await
questionsFeedReference.orderBy("timeStamp",descending:
true).getDocuments();
setState(() {
List<Widget> allQuestions=querySnapshot.documents.where((element) {
if(element["type"]=="question" ){
x=true;
return true;
}
else if(element["type"]=="answer"){
x=false;
return true;
}
else{
return false;
}
}).map((document)=>x?Question.fromDocument(document):
StreamBuilder<DocumentSnapshot>(
stream: answersReference.document("038281b3-3842-4ea9-b4d8-
3155a571826e").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
}
NotusDocument _document;
ZefyrController _zefyrController;
FocusNode _focusNode;
final messages = snapshot.data;
final messageText = messages.data['answer'];
_document =NotusDocument.fromJson( jsonDecode(messageText));
_zefyrController = ZefyrController(_document);
_focusNode=FocusNode();
return Container(
height: 400,
child: ZefyrScaffold(
child: ZefyrEditor(
padding: EdgeInsets.all(16),
controller: _zefyrController,
mode: ZefyrMode.view,
focusNode: _focusNode,
imageDelegate: MyAppZefyrImageDelegate(),
physics: NeverScrollableScrollPhysics(),
),
),
);
},
)
).toList();
this.questions=allQuestions;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
retrieveTimeLine();
}
createUserTimeLine(){
retrieveTimeLine();
if(questions==null){
return circularProgress();
}
else{
return ListView(children: questions,);
}
}
#override
Widget build(context) {
return Scaffold(
key: _scaffoldKey,
appBar: header(
context,
isAppTitle: true,
),
body: RefreshIndicator(
child: createUserTimeLine(),onRefresh()=>retrieveTimeLine(),),
);
}
}
I am using Zefyr Editor and then retriving its data and showing it in Container

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

navigation transition hangs during Future

i have the below code , that when the button is pressed on Page1() , the future is executed when Page2() loads, but the CircularProgressIndicator() "freezes", until the future completes. I have tried this with BottomNavigationBar as well, and the "slide-in" freezes half way there as well.
is there a more idiomatic way to do this so that Page2() renders fully while the future is running ?
//-----------------------
//main.dart
//-----------------------
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
routes: {
'/' : (BuildContext context) => Page1(),
'/page2' : (BuildContext context) => Page2()
}
);
}
}
//-----------------------
//page1.dart
//-----------------------
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page 1')),
body: Container(
child: Column(children: <Widget>[
Text('Page 1 header'),
RaisedButton(
child: Text('Click me'),
onPressed: () {
Navigator.of(context).pushReplacementNamed('/page2');
})
],),)
);
}
}
//-----------------------
//page2.dart
//-----------------------
class Page2 extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _Page2State();
}
}
class _Page2State extends State<Page2> {
MainModel model = MainModel();
void initState() {
model.fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page 2')),
body: ScopedModel<MainModel>(
model: model,
child: ScopedModelDescendant(
builder: (context, child, MainModel model) {
if (model.isLoading) {
return Center(child: CircularProgressIndicator());
} else {
return Container(
child: Column(
children: <Widget>[
Text('Page 2 body'),
],
));
}
})));
}
}
//-----------------------
//main_model.dart
//-----------------------
class MainModel extends Model {
bool _isLoading = false;
bool get isLoading => _isLoading;
void fetchData() async {
_isLoading = true;
notifyListeners();
final String url = 'https://jsonplaceholder.typicode.com/todos/1';
await http.get(url)
.then<Null>((http.Response response) {
print('${DateTime.now()} In http response and about to sleep');
sleep(const Duration(seconds:5));
print('${DateTime.now()} done sleeping');
_isLoading = false;
notifyListeners();
return;
}
).catchError((error) {
print('Error: $error');
_isLoading = false;
notifyListeners();
return;
});
}
}
The problem is that the sleep() method freezes the ui.
You could try a Future.delayed() in the following way:
class MainModel extends Model {
bool _isLoading = false;
bool get isLoading => _isLoading;
void fetchData() {
_isLoading = true;
notifyListeners();
final String url = 'https://jsonplaceholder.typicode.com/todos/1';
http.get(url).then<Null>((http.Response response) {
print('${DateTime.now()} In http response and about to sleep');
Future.delayed(Duration(seconds: 5), () {
_isLoading = false;
notifyListeners();
print('${DateTime.now()} done sleeping');
});
}).catchError((error) {
print('Error: $error');
_isLoading = false;
notifyListeners();
return;
});
}
}
Flutter recommends using the FutureBuilder widget when working with async data sources. For example:
FutureBuilder<Post>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner
return CircularProgressIndicator();
},
);
This way the CircularProgressIndicator will keep running whilst your Page2 loads the API data.