Provider Object Requests Rebuild During Existing Build - flutter

I'm learning Provider and my test app draws images from a Firestore database into a ListView. I'd like a LongPress on any image to make the whole list toggle and redraw with checkbox selector icons, as below, similar to the way the gallery works:
My code works, but it throws an exception on every LongPress stating that "setState() or markNeedsBuild() was called during build," and I'm pulling my hair out trying to figure out how to either delay the ChangeNotifier until the widget tree is built? Or some other way to accomplish this task?
My Provider class simply accepts a List of my PictureModel class and has a toggleSelectors() method which notifies listeners. Here's the code:
class PicturesProvider with ChangeNotifier {
List<PictureModel> _pictures = [];
bool visible = false;
UnmodifiableListView<PictureModel> get allPictures => UnmodifiableListView(_pictures);
UnmodifiableListView<PictureModel> get selectedPictures =>
UnmodifiableListView(_pictures.where((pic) => pic.selected));
void addPictures(List<PictureModel> picList) {
_pictures.addAll(picList);
notifyListeners();
}
void toggleSelectors() {
visible = !visible;
_pictures.forEach((pic) {
pic.selectVisible = visible;
});
notifyListeners();
}
}
I have a SinglePicture UI class that loads a network image into an AspectRatio widget and wraps it with a GestureDetector to toggle the selectors and present them on the top of a Stack widget, like so:
Widget build(BuildContext context) {
int originalHeight, originalWidth;
return AspectRatio(
aspectRatio: pictureModel.aspectRatio,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
FutureBuilder<ui.Image>(
future: _getImage(),
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
if (snapshot.hasData) {
ui.Image image = snapshot.data;
originalHeight = image.height;
originalWidth = image.width;
return GestureDetector(
onLongPress: () => Provider.of<PicturesProvider>(context, listen: false).toggleSelectors(),
child: RawImage(
image: image,
fit: BoxFit.cover,
// if portrait image, move down slightly for headroom
alignment: Alignment(0, originalHeight > originalWidth ? -0.2 : 0),
),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
Positioned(
left: 10.0,
top: 10.0,
child: pictureModel.selectVisible == false
? Container(
height: 0.0,
width: 0.0,
)
: pictureModel.selected == false
? Icon(
Icons.check_box_outline_blank,
size: 30.0,
color: Colors.white,
)
: Icon(
Icons.check_box,
size: 30.0,
color: Colors.white,
),
)
],
),
);
}
This SinglePicture class is then called from my PicturesList UI class which simply builds a ListView, like so:
class PicturesList extends StatelessWidget {
final List<PictureModel> pictures;
PicturesList({#required this.pictures});
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: pictures.length,
cacheExtent: 3,
itemBuilder: (context, index) {
return SinglePicture(
pictureModel: pictures[index],
);
},
);
}
The whole shebang is then called from a FutureBuilder in my app, which builds the app, like so:
body: FutureBuilder(
future: appProject.fetchProject(), // Snapshot of database
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// Get all picture URLs from project snapshot
List<dynamic> picUrls = snapshot.data['pictures'].map((pic) => pic['pic_cloud']).toList();
// Create list of PictureModel objects for Provider
List<PictureModel> pictures = picUrls.map((url) => PictureModel(imageUrl: url, imageHeight: 250.0, selectVisible: false)).toList();
// Add list of PictureModel objects to Provider for UI render
context.watch<PicturesProvider>().addPictures(pictures);
return SafeArea(
child: PicturesList(
pictures: context.watch<PicturesProvider>().allPictures,
),
);
} else if (snapshot.hasError) {
print('Error');
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
Please, if anybody has a hint about how I can accomplish this toggle action without throwing exceptions, I'd be very grateful. Thank you in advance!

Thanks to Remi Rousselet for the answer:
I have been using .builder methods wrong since the get-go and now need to go revisit ALL of my code and make sure they are clean.
To make this code work, I moved the Future out of my FutureBuilder and called it in the initState method, per Remi's guidance. I also had to create a new initializer method in my Provider class that did NOT notify listeners, so I could build the list for the first time.
Here are the code snippets to make my images 'selectable' with a LongPress and to be able to individually select them with a tap, as seen in the following image:
My PictureModel:
class PictureModel {
final String imageUrl;
final double aspectRatio;
double imageHeight;
bool selectVisible;
bool selected;
PictureModel({
#required this.imageUrl,
#required this.imageHeight,
this.aspectRatio = 4.0 / 3.0,
this.selectVisible = false,
this.selected = false,
});
#override
String toString() {
return 'Image URL: ${this.imageUrl}\n'
'Image Height: ${this.imageHeight}\n'
'Aspect Ratio: ${this.aspectRatio}\n'
'Select Visible: ${this.selectVisible}\n'
'Selected: ${this.selected}\n';
}
}
My PictureProvider model:
class PicturesProvider with ChangeNotifier {
List<PictureModel> _pictures = [];
bool visible = false;
UnmodifiableListView<PictureModel> get allPictures => UnmodifiableListView(_pictures);
UnmodifiableListView<PictureModel> get selectedPictures =>
UnmodifiableListView(_pictures.where((pic) => pic.selected));
void initialize(List<PictureModel> picList) {
_pictures.addAll(picList);
}
void addPictures(List<PictureModel> picList) {
_pictures.addAll(picList);
notifyListeners();
}
void toggleSelected(int index) {
_pictures[index].selected = !_pictures[index].selected;
notifyListeners();
}
void toggleSelectors() {
this.visible = !this.visible;
_pictures.forEach((pic) {
pic.selectVisible = visible;
});
notifyListeners();
}
}
My SinglePicture UI class:
class SinglePicture extends StatelessWidget {
final PictureModel pictureModel;
const SinglePicture({Key key, this.pictureModel}) : super(key: key);
Future<ui.Image> _getImage() {
Completer<ui.Image> completer = new Completer<ui.Image>();
new NetworkImage(pictureModel.imageUrl).resolve(new ImageConfiguration()).addListener(
new ImageStreamListener(
(ImageInfo image, bool _) {
completer.complete(image.image);
},
),
);
return completer.future;
}
#override
Widget build(BuildContext context) {
int originalHeight, originalWidth;
return AspectRatio(
aspectRatio: pictureModel.aspectRatio,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
FutureBuilder<ui.Image>(
future: _getImage(),
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
if (snapshot.hasData) {
ui.Image image = snapshot.data;
originalHeight = image.height;
originalWidth = image.width;
return RawImage(
image: image,
fit: BoxFit.cover,
// if portrait image, move down slightly for headroom
alignment: Alignment(0, originalHeight > originalWidth ? -0.2 : 0),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
Positioned(
left: 10.0,
top: 10.0,
child: pictureModel.selectVisible == false
? Container(
height: 0.0,
width: 0.0,
)
: pictureModel.selected == false
? Icon(
Icons.check_box_outline_blank,
size: 30.0,
color: Colors.white,
)
: Icon(
Icons.check_box,
size: 30.0,
color: Colors.white,
),
)
],
),
);
}
}
My PicturesList UI class:
class PicturesList extends StatelessWidget {
PicturesList(this.listOfPics);
final List<PictureModel> listOfPics;
#override
Widget build(BuildContext context) {
context.watch<PicturesProvider>().initialize(listOfPics);
final List<PictureModel> pictures = context.watch<PicturesProvider>().allPictures;
return ListView.builder(
itemCount: pictures.length,
cacheExtent: 3,
itemBuilder: (context, index) {
return GestureDetector(
onLongPress: () => Provider.of<PicturesProvider>(context, listen: false).toggleSelectors(),
onTap: () {
if (Provider.of<PicturesProvider>(context, listen: false).visible) {
Provider.of<PicturesProvider>(context, listen: false).toggleSelected(index);
}
},
child: SinglePicture(
pictureModel: pictures[index],
),
);
},
);
}
}
And last but not least, the FutureBuilder in the app from where all of this was called...
body: FutureBuilder(
future: projFuture,
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// Get all picture URLs from project snapshot
List<dynamic> picUrls = snapshot.data['pictures'].map((pic) => pic['pic_cloud']).toList();
// Create list of PictureModel objects for Provider
List<PictureModel> pictures = picUrls
.map((url) => PictureModel(imageUrl: url, imageHeight: 250.0, selectVisible: false))
.toList();
// Add list of PictureModel objects to Provider for UI render
// context.watch<PicturesProvider>().addPictures(pictures);
return SafeArea(
child: PicturesList(pictures),
);
} else if (snapshot.hasError) {
print('error');
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
Sorry for the long follow-up, but I figured I'd try to detail as much as possible how to make this work, in case it is useful to anybody else. Also, if anybody has further suggestions on how to improve this code, PLEASE let me know.
Thanks in advance.

Related

how to return value from onTap function in flutter

I am new to programming and flutter.
I am facing an error while returning a value from the onTap function.
The error says The return type 'int' isn't a 'void', as required by the closure's context.
I am getting images from API and showing them on a screen. I want that when someone clicks on an image, the image id should be stored in a variable that will be used on another screen. I am using Gesturedetector's onTap function to get the image id and I am successful to print that id into the terminal and store it in a variable but I am unable to return that variable and want to use that variable on another screen. Anyone, please help me to return the value of that variable. My code is below
import 'package:flutter/material.dart';
import 'Service.dart';
import 'View2.dart';
import 'models/memegenetor_model.dart';
class template_view extends StatefulWidget {
template_view({Key? key}) : super(key: key);
#override
State<template_view> createState() => _template_viewState();
}
class _template_viewState extends State<template_view> {
late Future<Memes_Model> futureScore;
#override
void initState() {
super.initState();
futureScore = ApiService().GetTemplates();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: FutureBuilder<Memes_Model>(
future: futureScore,
builder: (context, snapshot) {
if (snapshot.data != null) {
return ListView.builder(
itemCount: snapshot.data!.data.memes.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.only(left: 20, right: 20, top: 20),
child: Container(
color: Colors.red,
width: MediaQuery.of(context).size.width,
//height: 100,
child: GestureDetector(
onTap: (){
int tempid=snapshot.data!.data.memes[index].id;
return tempid;
},
child: Column(
children: [
Image.network(
"${snapshot.data!.data.memes[index].url}",
width: MediaQuery.of(context).size.width * 0.2),
],
),
)),
);
});
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
),
));
}
}
You can't return a value from onTap. Create a new state in your class.
class _template_viewState extends State<template_view> {
int? tempid = null; // or int tempid = -1;
// ...
}
onTap should look like this:
onTap: () {
tempid = snapshot.data!.data.memes[index].id; // use setState if require
},

ListView infinite loop when parsing data from API response

I'm trying to read data from some mock endpoint. Mock endpoint I'm invoking (HTTP GET) is here.
Essentially, the JSON structure is result > toolList[] > category > tools[]. I'd like to display these items on my page in such a way that the category name is displayed first, then items belonging to that category under it. I am trying to achieve this with ListView.builder but I somehow managed to get some sort of infinite loop and the items keep getting populated until my device freezes.
What I'm trying to achieve:
Category Title
Item 1
Item 2
Category Title 2
Item 1
Item 2
Itme 3
And finally the Widget:
class OpticsSelectorWidget extends StatefulWidget {
const OpticsSelectorWidget({Key key}) : super(key: key);
#override
_OpticsSelector createState() => _OpticsSelector();
}
class _OpticsSelector extends State<OpticsSelectorWidget> {
PageController pageViewController;
final scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: ConfigurationController.getOpticsTools2(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
for (int i = 0; i < toolCategories.length; i++) {
var currentToolCategory = getJsonField(
toolCategories[i],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length - 1; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}
}
I'm especially confused about the itemIndex expression. That number I thought would be the item count that I receive from my API call, but I guess I'm mixing something badly.
If it helps, here's the bit where I'm making the API call. But feel free to just grab the JSON your way (from mock response)
static Future<ApiCallResponse> getOpticsTools2() async {
HttpOverrides.global = new MyHttpOverrides();
var client = http.Client();
try {
var response = await client.get(Uri.https('stoplight.io'
, "mocks/ragingtortoise/test/82311857/configuration/tools/optics"));
return createResponse(response, true);
} finally {
client.close();
}
}
static ApiCallResponse createResponse(http.Response response, bool returnBody) {
var jsonBody;
try {
jsonBody = returnBody ? json.decode(response.body) : null;
} catch (_) {}
return ApiCallResponse(jsonBody, response.statusCode);
}
And the return type, which is ApiCallResponse:
class ApiCallResponse {
const ApiCallResponse(this.jsonBody, this.statusCode);
final dynamic jsonBody;
final int statusCode;
bool get succeeded => statusCode >= 200 && statusCode < 300;
}
Finally adding the screen recording of what's happening, if it helps.
In here builder you should use,itemCount parameter
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return Your Widget;
}),
Create a state variable for future and include itemCount: list.length,
final myFuture = ConfigurationController.getOpticsTools2();
And use it on
child: FutureBuilder<ApiCallResponse>(
future: myFuture ,
builder: (context, snapshot) {
I struggled for so long but clearly, the issue was not passing in the itemCount argument into the ListView.builder() method. Also, the outer loop was invalid as now I need to use the actual itemIndex within the builder. Thanks for pointing out the itemCount all! Here's the fixed code and the solution in case anyone needs it later.
#override
Widget build(BuildContext context) {
final opticsToolsMockResponse = ConfigurationController.getOpticsTools2();
return Scaffold(
backgroundColor: Colors.black,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: opticsToolsMockResponse,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(
itemCount: toolCategories.length,
itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
var currentToolCategory = getJsonField(
toolCategories[itemIndex],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}
You just forgot to specify the size of the list, you should do it with the itemCount property in the ListView.builder widget
itemCount: list.length,

Flutter : Image.memory widget unable to load

We are trying to create custom gallery page, so that we have followed some tutorial website but facing some issue unable to compile the project.
Tutorial link ::
https://medium.com/#mhstoller.it/how-to-create-a-custom-media-picker-in-flutter-to-select-photos-and-videos-from-the-gallery-988eea477643
class MediaGrid extends StatefulWidget {
#override
_MediaGridState createState() => _MediaGridState();
}
class _MediaGridState extends State<MediaGrid> {
List<Widget> _mediaList = [];
int currentPage = 0;
late int lastPage;
#override
void initState() {
super.initState();
_fetchNewMedia();
}
_handleScrollEvent(ScrollNotification scroll) {
if (scroll.metrics.pixels / scroll.metrics.maxScrollExtent > 0.33) {
if (currentPage != lastPage) {
_fetchNewMedia();
}
}
}
Future<ui.Image> loadImage(Uint8List img) async {
final Completer<ui.Image> completer = Completer();
ui.decodeImageFromList(img, (ui.Image img) {
return completer.complete(img);
});
return completer.future;
}
static Future<ui.Image> bytesToImage(Uint8List imgBytes) async{
ui.Codec codec = await ui.instantiateImageCodec(imgBytes);
ui.FrameInfo frame = await codec.getNextFrame();
return frame.image;
}
_fetchNewMedia() async {
lastPage = currentPage;
final PermissionState _ps = await PhotoManager.requestPermissionExtend();
if (_ps.isAuth) {
// success
//load the album list
List<AssetPathEntity> albums =
await PhotoManager.getAssetPathList(onlyAll: true);
print(albums);
List<AssetEntity> media =
await albums[0].getAssetListPaged(page: currentPage, size: 60);
// await albums[0].getAssetListPaged(currentPage, 60);
List<Widget> temp = [];
for (var asset in media) {
temp.add(
FutureBuilder(
future: asset.thumbnailDataWithSize(const ThumbnailSize(200, 200)),
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Stack(
children: [
// Image.asset(
// "assets/images/gallery.png",
// fit: BoxFit.cover,
// ),
Positioned.fill(
child: Image.memory(
snapshot.data,
fit: BoxFit.cover,
),
),
if (asset.type == AssetType.video)
const Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.only(right: 5, bottom: 5),
child: Icon(
Icons.videocam,
color: Colors.white,
),
),
),
],
);
}
return Container();
},
),
);
}
setState(() {
_mediaList.addAll(temp);
currentPage++;
});
} else {
// fail
/// if result is fail, you can call `PhotoManager.openSetting();` to open android/ios applicaton's setting to get permission
}
}
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scroll) {
return _handleScrollEvent(scroll);
},
child: GridView.builder(
itemCount: _mediaList.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return _mediaList[index];
}),
);
}
}
The following code not working,
Positioned.fill(
child: Image.memory(
snapshot.data,**//Error here**
fit: BoxFit.cover,
),
),
I have updated the dependencies to photo_manager 2.1.2 and changed required method accordingly.
Finally its working after Type Casting it.
snapshot.data as Uint8List,
Positioned.fill(
child: Image.memory(
snapshot.data! as Uint8List,
fit: BoxFit.cover,
),
),

Simple grid gallery with photo_manager?

I have been trying to recreate this (at the bottom of the page) but with flutter version 2.12.0 and the package photo_manager. I have tried to automatically do this, but unfortunately, I am not able to resolve the following part to the newer dart syntax.
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return Stack(
children: <Widget>[
Positioned.fill(
child: Image.memory(
snapshot.data, //wrong data type here
fit: BoxFit.cover,
),
),
In the orignal written code (for 2.7) it worked perfectly fine.
Simply call GridGallery() as a normal widget. Make sure to set up permissions to access the device gallery before. Have a look at the build Widget to configure the grid.
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:photo_manager/photo_manager.dart';
class GridGallery extends StatefulWidget {
final ScrollController? scrollCtr;
const GridGallery({
Key? key,
this.scrollCtr,
}) : super(key: key);
#override
_GridGalleryState createState() => _GridGalleryState();
}
class _GridGalleryState extends State<GridGallery> {
List<Widget> _mediaList = [];
int currentPage = 0;
int? lastPage;
#override
void initState() {
super.initState();
_fetchNewMedia();
}
_handleScrollEvent(ScrollNotification scroll) {
if (scroll.metrics.pixels / scroll.metrics.maxScrollExtent > 0.33) {
if (currentPage != lastPage) {
_fetchNewMedia();
}
}
}
_fetchNewMedia() async {
lastPage = currentPage;
final PermissionState _ps = await PhotoManager.requestPermissionExtend();
if (_ps.isAuth) {
// success
//load the album list
List<AssetPathEntity> albums =
await PhotoManager.getAssetPathList(
onlyAll: true);
print(albums);
List<AssetEntity> media =
await albums[0].getAssetListPaged(size: 60, page: currentPage); //preloading files
print(media);
List<Widget> temp = [];
for (var asset in media) {
temp.add(
FutureBuilder(
future: asset.thumbnailDataWithSize(ThumbnailSize(200, 200)), //resolution of thumbnail
builder:
(BuildContext context, AsyncSnapshot<Uint8List?> snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return Container(
child: Stack(
children: <Widget>[
Positioned.fill(
child: Image.memory(
snapshot.data!,
fit: BoxFit.cover,
),
),
if (asset.type == AssetType.video)
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.only(right: 5, bottom: 5),
child: Icon(
Icons.videocam,
color: Colors.white,
),
),
),
],
),
);
return Container();
},
),
);
}
setState(() {
_mediaList.addAll(temp);
currentPage++;
});
} else {
// fail
/// if result is fail, you can call `PhotoManager.openSetting();` to open android/ios applicaton's setting to get permission
}
}
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scroll) {
_handleScrollEvent(scroll);
return false;
},
child: GridView.builder(
controller: widget.scrollCtr,
itemCount: _mediaList.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return _mediaList[index];
}),
);
}
}

Flutter How to Populate ListView on app launch with sqflite?

I'm trying to display data in a ListView with a FutureBuilder. In debug mode, when I launch the app, no data is displayed, but, if I reload the app (hot Reload or hot Restart), the ListView displays all the data. I already tried several approaches to solve this - even without a FutureBuilder, I still haven't succeeded. If I create a button to populate the ListView, with the same method "_getregistos()", the ListView returns the data correctly.
This is the code I'm using:
import 'package:flutter/material.dart';
import 'package:xxxxx/models/task_model.dart';
import 'package:xxxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
dynamic tasks;
final textController = TextEditingController();
_getRegistos() async {
List<TaskModel> taskList = await _todoHelper.getAllTask();
// print('DADOS DA tasklist: ${taskList.length}');
return taskList;
}
TaskModel currentTask;
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getRegistos(),
builder: (context, snapshot) {
if (snapshot.hasData) {
tasks = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: tasks == null ? 0 : tasks.length,
itemBuilder: (BuildContext context, int index) {
TaskModel t = tasks[index];
return Card(
child: Row(
children: <Widget>[
Text('id: ${t.id}'),
Text('name: ${t.name}'),
IconButton(
icon: Icon(Icons.delete), onPressed: () {})
],
),
);
},
);
}
return Loading();
}),
],
),
),
);
}
}
Thank you.
You need to use ConnectionState inside your builder. Look at this code template: (Currently your builder returns ListView widget without waiting for the future to complete)
return FutureBuilder(
future: yourFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return _buildErrorWidget();
}
// return data widget
return _buildDataWidget();
// return loading widget while connection state is active
} else
return _buildLoadingWidget();
},
);
Thanks for your help.
I already implemented ConnectionState in the FutureBuilder and the issue persists.
When I launch the app, I get error "ERROR or No-Data" (is the message I defined in case of error of no-data.
If I click on the FlatButton to call the method "_getTasks()", the same method used in FutureBuilder, everything is ok. The method return data correctly.
This is the code refactored:
import 'package:flutter/material.dart';
import 'package:xxxx/models/task_model.dart';
import 'package:xxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
final textController = TextEditingController();
Future<List<TaskModel>> _getTasks() async {
List<TaskModel> tasks = await _todoHelper.getAllTask();
print('Tasks data: ${tasks.length}');
return tasks;
}
TaskModel currentTask;
//list to test with the FlatButton List all tasks
List<TaskModel> tasksList = [];
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//when clicking on this flatButton, I can populate the taskList
FlatButton(
child: Text('Show all Tasks'),
onPressed: () async {
List<TaskModel> list = await _getTasks();
setState(() {
tasksList = list;
print(
'TaskList loaded by "flatButton" has ${tasksList.length} rows');
});
},
color: Colors.red,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getTasks(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return Text('ERROR or NO-DATA');
}
// return data widget
return ListItems(context, snapshot.data);
// return loading widget while connection state is active
} else
return Loading();
},
),
],
),
),
);
}
}
//*****************************************
class ListItems extends StatelessWidget {
final List<TaskModel> snapshot;
final BuildContext context;
ListItems(this.context, this.snapshot);
#override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemCount: snapshot == null ? 0 : snapshot.length,
itemBuilder: (context, index) {
TaskModel t = snapshot[index];
return Text(' ${t.id} - ${t.name}');
}),
);
}
}