Null check operator used on a null value || Flutter - flutter

The null check operator used on a null value error comes up sometimes on the UI otherwise a circular progress indicator shows up.
the error shows up in the 266 line.
class Orders extends StatefulWidget {
#override
_OrdersState createState() => _OrdersState();
}
class _OrdersState extends State<Orders> {
OrdersModel? order;
OrdersModel? order2;
void modelData() async {
order2 = await ordersModel();
setState(() {
order = order2;
});
}
#override
void initState() {
modelData();
_futureOrdersModel = ordersModel();
// ordersModel();
super.initState();
}
Widget categories() {
return buildFutureBuilder();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: primaryColor,
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
);
}
late Future<OrdersModel>? _futureOrdersModel;
FutureBuilder<OrdersModel> buildFutureBuilder() {
return FutureBuilder<OrdersModel>(
future: _futureOrdersModel,
builder: (context, snapshot) {
if (snapshot.hasData) {
bool? checkedValue =
order?.data.attributes.totalBills[0].manualBillCompletion
?? true;
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: snapshot.data?.data.attributes.totalBills.length ?? 0,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => Review()));
},
],
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.vertical(top: Radius.circular(15))),
),
); //categoryCard1();
},
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return Column(
children: [
Center(child: const CircularProgressIndicator()),
],
);
},
);
}
}
check my console error to get it more clear of my errors
in console, it shows error due to bottomNavigation here is the code for that also

this null check error because I am using bang operator (!) to suppress the error. But on run time when my app runs this error shows up.
example:- How to use ? and ?? operator properly
String? name; //Here name is a nullable variable
void main()
{
var len = name?.length?? 0;
}
if the name is null then the value will be appointed 0.
so in the itemCount of the listview.builder I used ?? operator like this.
itemCount: customer?.data.length?? 0,

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
},

When I switch tabs, the content (StreamBuilder) of the previous tab is lost, leaving a blank page

I have two tabs, in each one the content is a StreamBuilder.
The StreamBuilder returns a column with a text field to search on a ListView and another StreamBuilder, the latter returns a ListView. When I move to another tab and subsequently return to the previous one, leaving a blank page.
When I move to another tab and subsequently return to the previous one, the content is lost, leaving a blank page. How can you avoid this?
Here is the relevant code:
class ListaCredencialesCapturistaPantalla extends StatefulWidget {
static const String id = "ListaCredencialesCapturistaPantalla";
const ListaCredencialesCapturistaPantalla({Key? key}) : super(key: key);
#override
_ListaCredencialesCapturistaPantallaState createState() => _ListaCredencialesCapturistaPantallaState();
}
class _ListaCredencialesCapturistaPantallaState
extends State<ListaCredencialesCapturistaPantalla>
with SingleTickerProviderStateMixin {
final List<CredencialModelo> _listaCredencialesActivos = <CredencialModelo>[];
final List<CredencialModelo> _listaCredencialesFinados = <CredencialModelo>[];
final StreamController<List<CredencialModelo>> _controladorStreamActivos = StreamController<List<CredencialModelo>>();
Stream<List<CredencialModelo>> get _streamActivos => _controladorStreamActivos.stream;
final StreamController<List<CredencialModelo>> _controladorStreamFinados = StreamController<List<CredencialModelo>>();
Stream<List<CredencialModelo>> get _streamFinados => _controladorStreamFinados.stream;
bool get wantKeepAlive => true;
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controladorStreamActivos.close();
_controladorStreamFinados.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return _construyeInterfaz();
}
Widget _construyeInterfaz() {
return DefaultTabController(
length: 2,
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: const Text('Listado de Credenciales'),
bottom: const TabBar(
tabs: <Widget>[
Tab(
icon: Constantes.ICONO_USUARIO_ACTIVO,
text: 'Activo',
),
Tab(
icon: Constantes.ICONO_USUARIO_FINADO,
text: 'Finado',
),
],
),
),
body: TabBarView(
children: <Widget>[
_construyeSeccionBusquedaListado(EstatusUsuario.activo),
_construyeSeccionBusquedaListado(EstatusUsuario.finado),
],
),
floatingActionButton: FloatingActionButton(
child: Constantes.ICONO_AGREGAR,
onPressed: () {
Navigator.pushNamed(context, AgregaCredencialPantalla.id);
},
),
),
);
}
Widget _construyeSeccionBusquedaListado(EstatusUsuario estatusUsuario) {
return StreamBuilder(
stream: Firestore.listaCredenciales(estatusUsuario),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.data!.docs.isEmpty) {
return Center(
child: Column(
children: const <Widget>[
Icon(FontAwesomeIcons.exclamationCircle),
Text('Sin credenciales'),
],
),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(color: Constantes.COLOR_INTERFAZ,),
);
}
List<CredencialModelo> listaCredenciales = <CredencialModelo>[];
for (QueryDocumentSnapshot<Map<String, dynamic>> elemento in snapshot.data!.docs) {
listaCredenciales.add(CredencialModelo.fromMapCredencial(elemento.data()));
}
if (estatusUsuario == EstatusUsuario.activo) {
_listaCredencialesActivos.clear();
_listaCredencialesActivos.addAll(listaCredenciales);
}
else {
_listaCredencialesFinados.clear();
_listaCredencialesFinados.addAll(listaCredenciales);
}
return _construyeBusquedaListado(estatusUsuario);
}
);
}
Widget _construyeBusquedaListado(EstatusUsuario estatusUsuario) {
return Column(
children: [
_construyeCampoBusqueda(estatusUsuario),
_construyeListaFiltrable(
estatusUsuario,
estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
),
],
);
}
Widget _construyeCampoBusqueda(EstatusUsuario estatusUsuario) {
return RoundedIconTextFormField(
textCapitalization: TextCapitalization.characters,
inputFormatters: [FormateoTextoMayusculasInput()],
labelText: 'Búsqueda',
prefixIcon: Constantes.DATO_ICONO_BUSQUEDA,
onChanged: (String busqueda) {
_busquedaCredencial(busqueda.toUpperCase(), estatusUsuario);
},
validator: (String? busqueda) {},
);
}
void _busquedaCredencial(String busqueda, EstatusUsuario estatusUsuario) {
if (busqueda.isNotEmpty) {
List<CredencialModelo> listaResultados =
(estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
).where(
(elemento) {
return elemento.curp!.contains(busqueda) || elemento.apePat!.contains(busqueda) ||
elemento.apeMat!.contains(busqueda) || elemento.nombre!.contains(busqueda) ||
elemento.municipio!.toString().contains(busqueda);
}
).toList();
(estatusUsuario == EstatusUsuario.activo ?
_controladorStreamActivos :
_controladorStreamFinados).sink.add(listaResultados);
}
else {
(estatusUsuario == EstatusUsuario.activo ?
_controladorStreamActivos :
_controladorStreamFinados
)
.sink
.add(estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
);
}
}
Widget _construyeListaFiltrable(EstatusUsuario estatusUsuario, List<CredencialModelo> listaCredenciales) {
return StreamBuilder<List<CredencialModelo>>(
key: ValueKey(listaCredenciales),
initialData: listaCredenciales,
stream: estatusUsuario == EstatusUsuario.activo ? _streamActivos : _streamFinados,
builder: (BuildContext context, AsyncSnapshot<List<CredencialModelo>> snapshot) {
if (snapshot.data!.isEmpty) {
return Center(
child: Column(
children: const <Widget>[
Icon(FontAwesomeIcons.exclamationCircle),
Text('Sin resultados'),
],
),
);
}
return _construyeListaCredenciales(snapshot.data!);
},
);
}
Widget _construyeListaCredenciales(List<CredencialModelo> listaCredenciales) {
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: listaCredenciales.length,
itemBuilder: (context, position) {
return _construyeCredencial(listaCredenciales[position]);
}
),
);
}
}
You can do it this way...
Create a StatefulWidget (it's technically a new page) instead of a simple Widget.
Something Like that:
class ConstruyeSeccionBusquedaListado extends StatefulWidget {
final EstatusUsuario estatusUsuario;
const ConstruyeSeccionBusquedaListado({Key? key, required this.estatusUsuario}) : super(key: key);
#override
State<ConstruyeSeccionBusquedaListado> createState() => _construyeSeccionBusquedaListado();
}
class _construyeSeccionBusquedaListado extends State<ConstruyeSeccionBusquedaListado> with AutomaticKeepAliveClientMixin<ConstruyeSeccionBusquedaListado> {
#override
Widget build(BuildContext context) {
return StreamBuilder<Object>(
stream: null,
builder: (context, snapshot) {
return Container();
}
);
}
#override
bool get wantKeepAlive => true;
}
And on your TabBarView do the following:
TabBarView(
children: [
ConstruyeSeccionBusquedaListado(estatusUsuario: EstatusUsuario.activo),
],
)
Don't forget to put the:
with AutomaticKeepAliveClientMixin<>
and the:
#override
bool get wantKeepAlive => true;
on your StatefulWidget.
This worked for me.
Source: https://github.com/flutter/flutter/issues/19116

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}');
}),
);
}
}

Future builder not firing

I can't seem to get my future builder to update. The api response is working fine I can see it in my logs. (model.getSuburbs). but it doesn't seem like my my future in the FutureBuilder suburbs is doing anything.. Am I missing something obvious (The onSubmitis trigger when I enter the last number and triggers the api)
class PostcodePage extends StatefulWidget {
static Route<dynamic> route() {
return MaterialPageRoute(
builder: (BuildContext context) => PostcodePage(),
);
}
#override
_PostcodeScreenState createState() => _PostcodeScreenState();
}
class _PostcodeScreenState extends State<PostcodePage> {
PostcodeViewmodel model = serviceLocator<PostcodeViewmodel>();
Future<List<Suburb>> suburbs;
String postCode;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(children: [
SizedBox(height: 200),
PinEntryField(
onSubmit: (input) => getSub(pc: input),
),
FutureBuilder<List<Suburb>>(
future: suburbs,
builder: (context, snapshot) {
if (snapshot.connectionState==ConnectionState.active) {
return Text('Would Like something here...');
} else
return Text('But always end up here...');
},
),
// (postCode != null) Text(postCode),
SizedBox(
height: 300,
),
SizedBox(
width: double.maxFinite,
child: OnBoardingButton(
text: 'Begin',
onPressed: () {},
color: Color(0xff00E6B9),
),
),
]),
),
);
}
getSub({String pc}) {
setState(() {
suburbs = model.getSuburbs(country: 'au', postcode: pc);
});
}
}
Try to change your condition inside the builder.
This code snapshot.connectionState==ConnectionState.active could be really really short depending on the suburbs future.
Please try this inside the builder.
if (snapshot.hasData) {
return Text('Would Like something here...');
} else {
return Text('But always end up here...');
}

pull down to REFRESH in Flutter

My dashboard code looks like this,
Here I am doing get req in getReport method, I have added the RefreshIndicator in the code which when pulled down inside container should do the refresh, there I am calling my getData(), But I am not getting the refreshed content, I am adding my code below, let me know if anywhere I made a mistake.
below my dashboard.dart
class Window extends StatefulWidget {
#override
_WindowState createState() => _WindowState();
}
class _WindowState extends State<Window> {
Future reportList;
#override
void initState() {
super.initState();
reportList = getReport();
}
Future<void> getReport() async {
http.Response response =
await http.get(reportsListURL, headers: {"token": "$token"});
switch (response.statusCode) {
case 200:
String reportList = response.body;
var collection = json.decode(reportList);
return collection;
case 403:
break;
case 401:
return null;
default:
return 1;
}
}
getRefreshScaffold() {
return Center(
child: RaisedButton(
onPressed: () {
setState(() {
reportList = getReport();
});
},
child: Text('Refresh, Network issues.'),
),
);
}
getDashBody(var data) {
double maxHeight = MediaQuery.of(context).size.height;
return Column(
children: <Widget>[
Container(
height: maxHeight - 800,
),
Container(
margin: new EdgeInsets.all(0.0),
height: maxHeight - 188,
child: new Center(
child: new RefreshIndicator( //here I am adding the RefreshIndicator
onRefresh:getReport, //and calling the getReport() which hits the get api
child: createList(context, data),
),),
),
],
);
}
Widget createList(BuildContext context, var data) {
Widget _listView = ListView.builder(
itemCount: data.length,
itemBuilder: (context, count) {
return createData(context, count, data);
},
);
return _listView;
}
createData(BuildContext context, int count, var data) {
var metrics = data["statistic_cards"].map<Widget>((cardInfo) {
var cardColor = getColorFromHexString(cardInfo["color"]);
if (cardInfo["progress_bar"] != null && cardInfo["progress_bar"]) {
return buildRadialProgressBar(
context: context,
progressPercent: cardInfo["percentage"],
color: cardColor,
count: cardInfo["value"],
title: cardInfo["title"],
);
} else {
return buildSubscriberTile(context, cardInfo, cardColor);
}
}).toList();
var rowMetrics = new List<Widget>();
for (int i = 0; i < metrics.length; i += 2) {
if (i + 2 < metrics.length)
rowMetrics.add(Row(children: metrics.sublist(i, i + 2)));
else
rowMetrics.add(Row(children: [metrics[metrics.length - 1], Spacer()]));
}
return SingleChildScrollView(
child: LimitedBox(
// maxHeight: MediaQuery.of(context).size.height / 1.30,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: rowMetrics,
),
),
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: reportList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
var data = snapshot.data;
if (snapshot.hasData && !snapshot.hasError) {
return getDashBody(data);
} else if (data == null) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Timeout! Log back in to continue"),
Padding(
padding: EdgeInsets.all(25.0),
),
RaisedButton(
onPressed: () {
setState(() {
token = null;
});
Navigator.of(context).pushReplacement(
CupertinoPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
child: Text('Login Again!'),
),
],
),
);
} else {
getRefreshScaffold();
}
}
},
);
}
}
Basic Example
Below is a State class of a StatefulWidget, where:
a ListView is wrapped in a RefreshIndicator
numbersList state variable is its data source
onRefresh calls _pullRefresh function to update data & ListView
_pullRefresh is an async function, returning nothing (a Future<void>)
when _pullRefresh's long running data request completes, numbersList member/state variable is updated in a setState() call to rebuild ListView to display new data
import 'package:flutter/material.dart';
import 'dart:math';
class PullRefreshPage extends StatefulWidget {
const PullRefreshPage();
#override
State<PullRefreshPage> createState() => _PullRefreshPageState();
}
class _PullRefreshPageState extends State<PullRefreshPage> {
List<String> numbersList = NumberGenerator().numbers;
#override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: _pullRefresh,
child: ListView.builder(
itemCount: numbersList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(numbersList[index]),
);
},),
),
);
}
Future<void> _pullRefresh() async {
List<String> freshNumbers = await NumberGenerator().slowNumbers();
setState(() {
numbersList = freshNumbers;
});
// why use freshNumbers var? https://stackoverflow.com/a/52992836/2301224
}
}
class NumberGenerator {
Future<List<String>> slowNumbers() async {
return Future.delayed(const Duration(milliseconds: 1000), () => numbers,);
}
List<String> get numbers => List.generate(5, (index) => number);
String get number => Random().nextInt(99999).toString();
}
Notes
If your async onRefresh function completes very quickly, you may want to add an await Future.delayed(Duration(seconds: 2)); after it, just so the UX is more pleasant.
This gives time for the user to complete a swipe / pull down gesture & for the refresh indicator to render / animate / spin indicating data has been fetched.
FutureBuilder Example
Here's another version of the above State<PullRefreshPage> class using a FutureBuilder, which is common when fetching data from a Database or HTTP source:
class _PullRefreshPageState extends State<PullRefreshPage> {
late Future<List<String>> futureNumbersList;
#override
void initState() {
super.initState();
futureNumbersList = NumberGenerator().slowNumbers();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<String>>(
future: futureNumbersList,
builder: (context, snapshot) {
return RefreshIndicator(
child: _listView(snapshot),
onRefresh: _pullRefresh,
);
},
),
);
}
Widget _listView(AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data[index]),
);
},);
}
else {
return Center(
child: Text('Loading data...'),
);
}
}
Future<void> _pullRefresh() async {
List<String> freshNumbers = await NumberGenerator().slowNumbers();
setState(() {
futureNumbersList = Future.value(freshNumbers);
});
}
}
Notes
slowNumbers() function is the same as in the Basic Example above, but the data is wrapped in a Future.value() since FutureBuilder expects a Future, but setState() should not await async data
according to Rémi, Collin & other Dart/Flutter demigods it's good practice to update Stateful Widget member variables inside setState() (futureNumbersList in FutureBuilder example & numbersList in Basic example), after its long running async data fetch functions have completed.
see https://stackoverflow.com/a/52992836/2301224
if you try to make setState async, you'll get an exception
updating member variables outside of setState and having an empty setState closure, may result in hand-slapping / code analysis warnings in the future
Not sure about futures, but for refresh indicator you must return a void so
Use something like
RefreshIndicator(
onRefresh: () async {
await getData().then((lA) {
if (lA is Future) {
setState(() {
reportList = lA;
});
return;
} else {
setState(() {
//error
});
return;
}
});
return;
},
Try this and let me know!
EDIT:
Well, then just try this inside you refresh method
setState(() {
reportList = getReport();
});
return reportList;
Try this:
onRefresh: () {
setState(() {});
}}
instead of onRefresh:getReport
reportList field is Future which returns its value once. So, when you call getReport again it changes nothing. Actually, more correctly it'll be with Stream and StreamBuilder instead of Future and FutureBuilder. But for this code it can be shortest solution
Easy method: you can just use Pull Down to Refresh Package - https://pub.dev/packages/pull_to_refresh
In Non-scrollable list view, RefreshIndicator does not work, so you have to wrap your widget with Stack for implementing pull down to refresh.
RefreshIndicator(
onRefresh: () {
// Refresh Functionality
},
child: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
)
],
),
// Your Widget
],
);
),
I am working on a huge project which contains CustomScrollView, NestedScrollView, ListView, etc I tried every answer above and all of the answers use RefreshIndicator from flutter SDK. It doesn't work entirely with my app because I also have horizontal scroll views. So in order to implement it I had to use NestedScrollView on almost every screen. Then I came to know about liquid_pull_to_refresh, applied it to the top widget, and WOLAAH! If you need a separate logic for each screen then use it at the top of each screen but in my case, I'm refreshing the whole project's data.