flutter await does not wait in initState - flutter

I'm new to flutter , so i'm not sure my way of coding is correct.
I'm trying to get data from firebase , and want to load it in initState().
But, it can not wait. whlie the process is in await, it starts to build.
How can I load data before build.
Thanks
These are the codes and logs.
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/entity/quiz.dart';
import '../result.dart';
class Question extends StatefulWidget {
#override
_QuestionState createState() => _QuestionState();
}
class _QuestionState extends State<Question> {
#override
void initState() {
print("init State");
super.initState();
Future(() async {
await Future.delayed(Duration(seconds: 5));
});
setQuiz();
}
List<Quiz> quiz = [];
Future<void> setQuiz() async {
// 検索結果は、一旦はFuture型で取得
Future<QuerySnapshot<Map<String, dynamic>>> snapshot =
FirebaseFirestore.instance.collection("quiz").get();
print("inSetQuiz");
// ドキュメントを取得
quiz = await snapshot.then(
(event) => event.docs.map((doc) => Quiz.fromDocument(doc)).toList());
// await snapshot.then((value) {
// setState(() {
// quiz = value.docs.map((doc) => Quiz.fromDocument(doc)).toList();
// });
// });
print("afterSetQuiz");
print("hoge" + quiz.length.toString());
print("hoge" + quiz.elementAt(0).sentence);
}
// 問題番号
int questionNumber = 0;
// 正解問題数
int numOfCorrectAnswers = 0;
#override
Widget build(BuildContext context) {
print("in build");
return Scaffold(
appBar: AppBar(
title: Text("問題ページ"),
),
body: Center(
child:
Column(mainAxisAlignment: MainAxisAlignment.start, children: [
Container(
height: 50,
color: Colors.red,
child: const Center(
child: Text(
"一旦",
style: TextStyle(fontSize: 20),
),
),
),
Container(
padding: const EdgeInsets.all(8.0),
child: Text(
quiz.elementAt(questionNumber).sentence, ←error happens
style: TextStyle(fontSize: 20),
)),
omit
here logs↓
flutter: init State
flutter: inSetQuiz
flutter: in build
It load build before await function.

You shouldn't be holding off the initState method. This message straight from a Flutter error says it all: "the initState method must be a void method without an async keyword; rather than waiting on asynchronous work directly inside of initState, just call a separate method to do this work without awaiting it".
That's what a FutureBuilder is for. I'd refactor the app this way:
Keep your setQuiz method async, but return not a void Future, but a Future that wraps the data this method returns (in your case, a quiz).
Future<List<Quiz>> setQuiz() {
// your existing code, just at the end do:
return quiz;
}
Feed the return of the setQuiz async method into a FutureBuilder widget:
#override
Widget build(BuildContext context) {
print("in build");
return Scaffold(
appBar: AppBar(
title: Text("問題ページ"),
),
body: FutureBuilder(
future: setQuiz(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// out of the FutureBuilder's snapshot, collect the data
var quiz = snapshot.data as List<Quiz>;
// build your quiz structure here
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 50,
color: Colors.red,
child: const Center(
child: Text("一旦",
style: TextStyle(fontSize: 20),
),
),
),
ListView.builder(
itemCount: quiz.length,
itemBuilder: (context, index) {
var singleQuestion = quiz[index];
return Text(singleQuestion.sentence);
}
)
]
)
);
}
// while waiting for data to arrive, show a spinning indicator
return CircularProgressIndicator();
}
)
);
}

Related

Future builder runs forever, if memoizer used doesnt notify to the listerners

I am trying to switch the drawer tab, according to the value stored in shared preferences using the following code.
code works fine when memoizer is not used but future builder runs forever.
If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets.
I need some way to stop running future builder forever and notify users as well accordingly by triggering get and set functions present in it
Notifier class
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
Drawer
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return Container(
width: 260,
child: Drawer(
child: Material(
color: Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Container(
padding: AppLandingView.padding,
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
if (snapshot.data == true) {
return _buildMenuItem(
text: 'widget1',
icon: Icons.add_business,
onTap: () {
provider.switchApp(false);
},
);
} else {
return _buildMenuItem(
text: 'widget2',
icon: Icons.add_business,
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
),
],
),
),
),
);
},
),
);
}
Scaffold
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: _buildDrawer(),
);
}
Update
I analysed further, problem lies in provider.getValue(), if i use notifyListeners() before returning the value future builder runs forever
I removed it and the future builder doesn't run forever, but it doesn't update other widgets.
Scenario is
widget 1
contains a drawer
has a button to switch app
on tap value is set using shared preferences (setValue() function) and listeners are notified
in widget 1 notifier is working well and changing the drawer button option when setValue() is called on tap.
everything resolved in widget 1, as its calling setValue() hence notifyListeners() is triggered and widget1 is rerendered
widget 2
only gets value from shared preferences(getValue() function). getValue function cant use notifyListeners(), if used futurebuilder is running forever
widget 2 don't set any value so it doesn't use setValue() hence it's not getting notified
how I can notify widget 2, when on tap setValue() is triggered in widget 1
i.e widget1 sets the app using setValue() function
widget2 gets value from getValue() function and get notified
Update 2
class SwitchAppProvider with ChangeNotifier {
dynamic _myValue;
dynamic get myValue => _myValue;
set myValue(dynamic newValue) {
_myValue = newValue;
notifyListeners();
}
setValue(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
SwitchAppProvider(){
getValue();
}
Future<void> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
myValue = prefs.getBool('key');
}
}
widget 2
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
if (provider.myValue == true) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
child: Text('provider.myValue'));
}
})
);
}
}
_buildMenuItem
// helper widget to build item of drawer
Widget _buildMenuItem({
required String text,
required IconData icon,
required GestureTapCallback onTap,
}) {
final color = Colors.white;
final hoverColor = Colors.white;
return ListTile(
leading: Icon(icon, color: color),
title: Text(text, style: TextStyle(color: color, fontSize: 18)),
hoverColor: hoverColor,
onTap: onTap,
);
}
"If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets."
That is the expected behaviour:
An AsyncMemoizer is used when some function may be run multiple times in order to get its result, but it only actually needs to be run once for its effect.
so prefs.setBool('key', value); is executed only the first time.
You definitely do not want to use it.
If you edit your code to remove the AsyncMemoizer, we can try to help you further.
Edit after Update
You are right, the getValue() function should not notify listeners, if it does that, then the listeners will rebuild and ask for the value again, which will notify listeners, which will rebuild and ask for the value again, which... (you get the point).
There is something wrong in your reasoning. widget1 and widget2 are not notified, the Consumer is notified. Which will rebuild everything. The code is quite complicated and it could be simplified a lot by removing unneeded widgets.
I will suggest you to
await prefs.setBool('isWhatsappBusiness', value); before notifying listeners.
have a look at this answer for a similar problem.
Edit 3
I do not know what you are doing wrong, but this works:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
),
);
}
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
},
),
);
}
}
If you still cannot get it working, then the problem is somewhere else.
Edit 4
First, I suggest you to be more clear in future questions. Write all the code that is needed immediately and remove widgets that are not needed. Avoid confusion given by naming different things in the same way.
The second widget does not update because it is listening to a different notifier.
When you do
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
in Widget2 you are creating a new provider object, you are not listening to changes in the provider you created in the Drawer.
You need to move the ChangeNotifierProvider.value widget higher in the widget tree, and use the same one:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
body: const Widget2(),
),
),
);
}
Widget _buildDrawer() {
return Consumer<SwitchAppProvider>(builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
});
}
}
class Widget2 extends StatelessWidget {
const Widget2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
return FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
print('Am I building even more ?');
if (snapshot.data == true) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return const Text('provider.myValue');
}
},
);
},
);
}
}

Flutter StreamBuilder not listening to new data?

I have two screens, one where the user can chat with a particular person and the second screen, where he can see a list of all the chats.
The aim is to display the last message on the second screen. This is done as follows:
The user sends/receives a new message?
Update the database
BloC sends a new stream of data by fetching the newest data.
The problem is, the stream builder isn't listening to the new data (not sure why). To the best of my knowledge, the BloC is sending a new stream of data when the user sends a message, it just doesn't re-render in the list.
Here's a shortened version of the code:
class ChatScreen extends StatelessWidget {
final ContactsBloc _contactsBloc = ContactsBloc();
#override()
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context, true);
},
),
body: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextController,
onChanged: (value) {
message = value;
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () async {
//update remote and local databases
await _contactsBloc.updateContact(
{'last_message': utf8.decode(base64.decode(message))},
'conversation_id = ?',
[conversationId]);
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
The chats screen:
class ChatsScreen extends StatefulWidget {
static const id = 'chats';
#override
_ChatsScreenState createState() => _ChatsScreenState();
}
class _ChatsScreenState extends State<ChatsScreen> {
final ContactsBloc _contactsBloc = ContactsBloc();
Iterable<Contact> contacts;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chats'),
body: Container(
child: StreamBuilder(
stream: _contactsBloc.contacts,
builder: (context, results) {
print('New stream: $results');
if (!results.hasData) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
),
],
);
} else {
List contacts = results.data;
contacts = contacts
.where((element) => element.lastMessage != null)
.toList();
if (contacts.length > 0) {
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
ContactModel contact = contacts[index];
return ChatItem(
name: contact.name,
message: contact.lastMessage,
profilePicture: contact.profilePictureUrl,
lastSeen: contact.lastSeen,
user: currentUser,
toUser: contact.uid,
conversationId: contact.conversationId,
);
},
);
}
return Container();
}
},
)),
);
}
}
The contact BloC:
class ContactsBloc {
ContactsBloc() {
getAllContacts();
}
final _contactsController = StreamController<List<ContactModel>>.broadcast();
Stream<List<ContactModel>> get contacts => _contactsController.stream;
_dispose() {
_contactsController.close();
}
getAllContacts() async {
List<ContactModel> contacts = await DatabaseProvider.db.getAllContacts();
_contactsController.sink.add(contacts);
}
updateContact(var update, String where, List whereArgs) async {
await DatabaseProvider.db.updateContact(update, where, whereArgs);
getAllContacts();
}
}
For now try adding this to create a Singleton instance of ContactBloc
class ContactsBloc{
ContactsBloc._();
static final ContactsBloc _instance = ContactsBloc._();
factory ContactsBloc() => _instance;
/// the rest of your code...
}
I would recommend checking some state management if you want more control of your classes (Bloc, Provider, Redux, etc)

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

Flutter- Error with Drawer and Listview.Builder()

Here's my code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
//import 'package:location/location.dart';
class Home extends StatefulWidget {
Home({Key key, this.title}) : super(key: key);
final String title;
#override
_Home createState() => _Home();
}
class _Home extends State<Home> {
//declared var
String key = "ville";
String txt_ville = "PARIS";
List<String> villes = [];
GlobalKey<ScaffoldState> _drawerKey = GlobalKey();
//Location location;
//LocationData locationData;
//Stream<LocationData> stream;
#override
void initState() {
// TODO: implement initState
getData();
print(villes);
super.initState();
//location = new Location();
//getFirstLocation();
}
/*getFirstLocation() async {
try {
print("Position: ${locationData.latitude}/${locationData.longitude}");
}catch (e) {
print("Error when locating $e");
}
}*/
#override
Widget build(BuildContext context) {
return Scaffold(
key: _drawerKey,
drawer: Drawer(
child: new ListView.builder(
itemCount: villes.length,
itemBuilder: (BuildContext ctx, i) {
return new ListTile(title: new Text(villes[i]));
},
)
),
backgroundColor: Colors.amberAccent,
body: new Column(
children: <Widget>[
Expanded(
flex:4,
child: new Container(
width: double.infinity,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(bottomLeft: Radius.circular(120))
),
child: new Column(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 50),
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Padding(padding: EdgeInsets.only(left: 20),
child: new IconButton(icon: new Icon(Icons.dehaze, size: 30, color: Colors.black,), onPressed: () {
_drawerKey.currentState.openDrawer();
})),
new Padding(
padding: EdgeInsets.only(left: 30),
child: new Text("TheWeatherApp", style: new TextStyle(
fontSize: 40
),),
),
]),
)
],
),
)
),
Expanded(
flex: 1,
child: new Container(
),
)
],
)
);
}
Future<Null> addTown() async{
return showDialog(barrierDismissible: true, context: context, builder: (BuildContext buildcontext) {
return new SimpleDialog(
contentPadding: EdgeInsets.all(20.0),
title: Text("Add a town"),
children: <Widget>[
new RaisedButton(onPressed: () {
}, child: new Text("Auto locate me"),),
new TextField(
decoration: new InputDecoration(
labelText: "Ville"
),
onSubmitted: (s) {
setData(s);
Navigator.pop(context);
},
)
],
);
});
}
void getData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
List<String> liste = await sharedPreferences.getStringList(key);
if (liste != null) {
setState(() {
villes = liste;
});
}
}
void setData(String str) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
villes.add(str);
await sharedPreferences.setStringList(key, villes);
getData();
}
void deleteData(String str) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
villes.remove(str);
await sharedPreferences.setStringList(key, villes);
}
}
I'm still a beginner on Flutter and I'm trying to understand why when I launch my application on the emulator and open my drawer with the iconbutton I get this error: Pastbinlink to error
If I deliberately create an error in the code like removing a parenthesis and do a hotreload, and I put the parenthesis back and do a hotreload again then my code works and the listview is displayed...
I have a theory that it's my getData function that initializes the variable villes that doesn't work...
I thank you in advance for any answer!
I can't comment, so I am writing it as an answer.
I haven't tried your code, and I am not sure if it will work, however,
You should try to use FutureBuilder
SharedPreferences.getInstance() is a future that will have value later on, and when you try to build your Drawer, it tries to access a value that doesn't exist.
Instead, if you try it like this. Your drawer will be created once your data is retrieved.
drawer: Drawer(
child: new FutureBuilder<List<String>>(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.length == 0) {
return Center(child: Text("No data found."));
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext ctx, i) {
return new ListTile(title: new Text(villes[i]));
},
)
}
} else if (snapshot.hasError) {
return checkError();
}
// By default, show a loading spinner.
return Center(child: CircularProgressIndicator());
})
),

How to update stateful widget in Navigation drawer while keeping same class like fragments in Android?

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.