Related
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,
I am making a Flutter application with SQLite database in it with the sqflite package. I have a database helper class with the necessary methods. In one of the pages I want to display the data as a list of cards. I also have an image stored to the database, as such in the same page I have to convert the image back to a File. In the class of that page, named DataPage, I made a method called query which calls the query method of the database and assigns that value to a list called listLokasi. I also made a method called convert which calls List.generate with one of the arguments being listLokasi.length. Meanwhile I placed these 2 methods inside the constructor for _DataPageState as DataPage is a stateful widget. The problem is when I run the app an error displayed showing a NoSuchMethodError as I tried to call length on null, this means listLokasi is null. So I placed asserts in the query method, in the constructor after the query method, and in the convert method. The results is the assert in the query method did not fire, while the assert in the constructor immediately fired. I have inspected my database helper class and reviewed my code and I cannot find the flaw in my code. Any help in this problem would be appreciated. I shall display the code below.
This is the database helper class.
class DatabaseHelper {
static final _instance = DatabaseHelper._internal();
DatabaseHelper._internal();
factory DatabaseHelper() {
return _instance;
}
Database db;
Future initDatabase() async {
var databasePath = await getDatabasesPath();
var path = join(databasePath, 'table.db');
db = await openDatabase(path, version: 1, onCreate: onCreate);
}
onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE lokasi
(id INTEGER PRIMARY KEY,
name TEXT,
description TEXT,
category TEXT,
latitude REAL,
longitude REAL,
image BLOB)
''');
}
Future<Lokasi> insert(Lokasi lokasi) async {
await db.insert('lokasi', lokasi.toJson());
return lokasi;
}
Future<List<Lokasi>> query() async {
var list = await db.query('lokasi');
return List.generate(list.length, (i) => Lokasi.fromJson(list[i]));
}
}
This is the DataPage class.
class DataPage extends StatefulWidget {
final savedUsername;
const DataPage({this.savedUsername = 'User'});
#override
_DataPageState createState() => _DataPageState();
}
class _DataPageState extends State<DataPage> {
DatabaseHelper db = DatabaseHelper();
List<Lokasi> listLokasi;
List<LokasiConvert> listLokasiConvert;
_DataPageState() {
query();
assert(listLokasi != null);
convert();
}
convert() {
assert(listLokasi != null);
listLokasiConvert = List.generate(
listLokasi.length,
(i) => LokasiConvert(
name: listLokasi[i].name,
description: listLokasi[i].category,
category: listLokasi[i].category,
latitude: listLokasi[i].latitude,
longitude: listLokasi[i].longitude,
image: File.fromRawPath(listLokasi[i].image),
),
);
}
Future<List<Lokasi>> query() async {
listLokasi = await db.query();
assert(listLokasi != null);
return listLokasi;
}
void sendUsername(BuildContext context) {
String username = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MainPage(username: username),
),
);
}
void sendUsernameToChart(BuildContext context) {
String chartUsername = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChartPage(savedUsername: chartUsername),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Data'),
actions: [
RaisedButton(
child: Text('Logout'),
onPressed: () {
Navigator.popUntil(context, ModalRoute.withName('/'));
},
),
RaisedButton(
child: Text('Main'),
onPressed: () {
sendUsername(context);
},
),
RaisedButton(
child: Text('Charts'),
onPressed: () {
sendUsernameToChart(context);
},
),
],
),
body: ListView.builder(
itemBuilder: (context, i) {
return Card(
child: Row(
children: [
Image.file(
listLokasiConvert[i].image,
width: 100,
height: 100,
),
Column(
children: [
Text(listLokasiConvert[i].name),
Text(listLokasiConvert[i].category),
Text(listLokasiConvert[i].description),
Text('Coordinates: ' +
listLokasiConvert[i].latitude.toString() +
', ' +
listLokasiConvert[i].longitude.toString()),
],
)
],
));
},
itemCount: listLokasiConvert.length,
),
);
}
}
Once again, thank you for any help given.
Maybe you can try this. Hope its help you.
db.query().then((value) {
setState(() {
listLokasi = value
});
});
The solution is actually simple as I discovered in my process. The list is a future so I am supposed to use a future builder and then wrap the listview builder with the future builder. Let me show my finished code of that specific page.
class DataPage extends StatefulWidget {
final savedUsername;
const DataPage({this.savedUsername = 'User'});
#override
_DataPageState createState() => _DataPageState();
}
class _DataPageState extends State<DataPage> {
DatabaseHelper db = DatabaseHelper();
List<Lokasi> listLokasi;
delete(value) async {
await db.delete(value);
}
Future<List<Lokasi>> query() async {
listLokasi = await db.query();
return listLokasi;
}
void sendUsername(BuildContext context) {
String username = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MainPage(username: username),
),
);
}
void sendUsernameToChart(BuildContext context) {
String chartUsername = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChartPage(savedUsername: chartUsername),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Data'),
actions: [
RaisedButton(
child: Text('Logout'),
onPressed: () {
Navigator.popUntil(context, ModalRoute.withName('/'));
},
),
RaisedButton(
child: Text('Main'),
onPressed: () {
sendUsername(context);
},
),
RaisedButton(
child: Text('Charts'),
onPressed: () {
sendUsernameToChart(context);
},
),
],
),
body: FutureBuilder(
future: query(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (context, i) {
return Card(
child: Row(
children: [
Column(
children: [
Text(snapshot.data[i].name),
Text(snapshot.data[i].category),
Text(snapshot.data[i].description),
Text('Coordinates: ' +
snapshot.data[i].latitude.toString() +
', ' +
snapshot.data[i].longitude.toString()),
],
),
Container(
width: 100,
height: 100,
child: Image.memory(snapshot.data[i].image),
),
Container(
width: 30,
height: 30,
child: IconButton(
onPressed: () {
db.delete(snapshot.data[i].name);
setState(() {});
},
icon: Icon(Icons.delete, size: 30),
iconSize: 30,
),
)
],
),
);
},
itemCount: snapshot.data.length,
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}
Any help would be grateful, I have no idea where is the issue. I have a splash page where based on AuthCheckState, if the user is registered he gets pushed to the HomeScreen or to the SignUp/Welcome screen.
When inside the SignUp page, If I successfully register with Firebase, my SignInForm listener is triggered and the user is pushed back to the SplashScreen and a new authCheckEvent is sent to my AuthCheck bloc.
My AuthCheck bloc then yields the updated state (authenticated), but the listener inside the SplashPage is not called.
class SplashPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocListener<AuthCheckBloc, AuthCheckState>(
listener: (context, state) {
state.map(
initial: (_) {
print("Inital STate");
},
authenticated: (_) {
print("Authenticated STate");
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
},
unauthenticated: (_) {
print("Unathenticated state STate");
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => WelcomePage(),
),
);
},
);
},
child: SafeArea(
child: Scaffold(
body: Center(
child: Column(
children: [
Spacer(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Image.asset(
"assets/logo.PNG",
width: MediaQuery.of(context).size.width * 0.5,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: CircularProgressIndicator(),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: const Text("Loading..."),
),
Spacer(),
],
),
),
),
),
);
}
}
class SignUpPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: BlocConsumer<SignInFormBloc, SignInFormState>(
listener: (context, state) {
if (state.errorMessage.isNotEmpty) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.errorMessage),
duration: Duration(seconds: 1),
),
);
}
if (state.isAuthStateChanged) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => SplashPage(),
),
);
//Add email verification? Show snackbar, asking to verify email, so that they can log in?
//Send back to WelcomeScreen, or send back to a temp screen where it waits for email verification?
context.read<AuthCheckBloc>().add(
const AuthCheckEvent.authCheckRequested(),
);
}
},
builder: //Sign Form UI here...
class AuthCheckBloc extends Bloc<AuthCheckEvent, AuthCheckState> {
final AuthRepository _authRepository;
AuthCheckBloc(this._authRepository) : super(const AuthCheckState.initial());
#override
Stream<AuthCheckState> mapEventToState(
AuthCheckEvent event,
) async* {
yield* event.map(
authCheckRequested: (e) async* {
print("auth check CALLLED");
final bool = _authRepository.isUserSignedIn();
if (bool) {
yield const AuthCheckState.authenticated();
print("yielded authenticated");
} else {
yield const AuthCheckState.unauthenticated();
}
},
signOutPressed: (e) async* {
String returnValue = await _authRepository.signOut();
if (returnValue == kSuccess) {
yield const AuthCheckState.unauthenticated();
} else {
print("There was an error signing out!");
}
},
);
}
Update: Didn't find where the issue was, so I changed my solution, had to make some sacrifices for it though.
I converted SplashPage to a Stateful widget, removed the event from SignUpPage and put the event authCheckRequested inside the SplashPage init() method, so every time my SplashPage is initialized it will perform a check and push to the appropriate screen.
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.
I want to update stateful widget of my class while returning same class after getting data from server, from navigation drawer. I am having issue that class loads data only one time and remain the same if I navigate to another item of my navigation drawer. Because the state is created only once.
Here is my code:
class CategoryFilter extends StatefulWidget {
int productIndex;
String category_name;
CategoryFilter(this.productIndex, this.category_name)
{
print("CategoryFilter");
print(productIndex);
print(category_name);
new _CategoryFilterState(productIndex, category_name);
}
#override
_CategoryFilterState createState() => new
_CategoryFilterState(productIndex, category_name);
}
class _CategoryFilterState extends State<CategoryFilter> {
int productIndex;
List<ResponseDataProducts> productList;
List data;
String category_name;
_CategoryFilterState(this.productIndex, this.category_name)
{
print("CategoryFilter");
print(productIndex);
print(category_name);
}
#override
void initState(){
super.initState();
Future<String> status = getData(productIndex);
status.then((onValue){
if(onValue.toString() == "Success")
{
Navigator.pop(context);
}
});
// this.getData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
color: Colors.white30,
child: new ListView.builder(
itemCount: productList == null ? 0 : productList.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
margin: const EdgeInsets.only( bottom: 10.0),
constraints: new BoxConstraints.expand(
height: 200.0
),
alignment: Alignment.bottomLeft,
decoration: new BoxDecoration(
image: new DecorationImage(image:
new NetworkImage
("http://myurl.com/"+productList[index].thumbnail),
fit: BoxFit.cover)
),
child:new Container(
child: new Text(
productList[index].name,
style: new TextStyle(color: Colors.white, fontSize: 30.0),
),
color: Colors.black54,
alignment: new FractionalOffset(0.5, 0.0),
height: 35.0,
// margin: const EdgeInsets.symmetric(vertical: 30.0),
),
);
})
),
) ;
}
void _onLoading()
{
showDialog(context: context,
barrierDismissible: false,
child: progress);
new Future.delayed(new Duration(seconds: 2), (){
// Navigator.pop(context);
});
}
Future<String> getData(int productIndex) async {
productList = new List<ResponseDataProducts>();
_onLoading();
http.Response response = await http.get(
Uri.encodeFull(CommonMethods.base_url + 'product/$productIndex'),
headers: {"Accept": "application/json"});
print(response.body);
setState(() {
var convertDataToJson = JSON.decode(response.body);
data = convertDataToJson["responseData"];
for(int i=0; i<data.length; i++)
{
ResponseDataProducts responseData = new ResponseDataProducts(
data[i]["id"],
data[i]["name"], data[i]["description"],
data[i]["title"], data[i]["thumbnail"]);
productList.add(responseData);
}
//Navigator.pop(context);
});
return "Success";
}
}
Here is how I am calling this categoryFilter class from Navigation Drawer:
_getDraserItemWidget(int pos)
{
switch(pos)
{
case 0:
return new Home(bar_id);
case 1:
return new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);
case 2:
return new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);
case 3:
return new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);
case 4:
return new OpeningTime();
case 5:
break;
}
}
I would suggest that instead of calling the method to load data within the initState method of your class, that you use a FutureBuilder widget. If you return a new FutureBuilder from your Navigation Drawer, that should call your service each time a new one is created, and is generally a better way of performing asynchronous requests anyways.
Here's a very simple example. It doesn't do the drawer very well (or a few other things - there's only so much time to spend on things like this), but it should illustrate the concept.
Note that rather than 'updating the widget' it simply creates a new widget. Because of the way flutter does things, this should be relatively performant, especially because you're not doing it all the time but rather only when the user selects something from the navigation menu.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new TextPage(text: "Home!"),
);
}
}
Map<int, int> _nums = Map();
class TextPage extends StatelessWidget {
final String text;
const TextPage({Key key, #required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new PreferredSize(
child: new Container(),
preferredSize: Size.fromHeight(10.0),
),
body: new Center(
child: new Text(text),
),
drawer: new Builder(
builder: (context) => Material(
child: new SafeArea(
child: Column(
children: <Widget>[
new FlatButton(
onPressed: () {
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => _getDrawerItemWidget(1)));
},
child: Text("First item"),
),
new FlatButton(
onPressed: () {
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => _getDrawerItemWidget(2)));
},
child: Text("Second item"),
),
],
),
),
),
),
);
}
_getDrawerItemWidget(int i) {
return new FutureBuilder<String>(
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.data != null) {
return new TextPage(text: snapshot.data);
} else {
return new TextPage(text: "Loading.");
}
},
future: () async {
var num = _nums.putIfAbsent(i, () => 0);
_nums[i] = num + 1;
String toReturn = "You retrieved number $i for the $num time";
return await Future.delayed<String>(Duration(seconds: 1), () => toReturn);
}(),
);
}
}
You could theoretically do something different with keeping GlobalKey references and using those to call a method on the child widget if it matches the current selection to have it update, but that's generally a bad idea in flutter - best practices encourage you to pass data downwards in the widget tree rather than call functions downwards. If you have to use GlobalKeys, you can generally refactor to do something better.