Passing Null Value when loading an async map - android

I'm trying to pass this map as a value for the next page route, but the value in jsonResult inside MaterialApp goes as a null.
Debugging, it is possible to see that the map contains information about the models. However, in the MaterialApp no, it only appears as a null:
JjModel jmodel = JjModel();
dynamic jsonResult;
loadJson() async {
Map<String, dynamic> mapInicial = jmodel.informacoesIniciais();
final Directory _appDocDir = await getApplicationDocumentsDirectory();
final Directory _appDocDirFolder =
Directory('${_appDocDir.path}/fileSettings');
File jsonFile = File('${_appDocDirFolder.path}/Preferences.json');
if (await jsonFile.exists()) {
String data = jsonFile.readAsStringSync();
jsonResult = json.decode(data);
} else {
final Directory _appDocDirNewFolder =
await _appDocDirFolder.create(recursive: true);
File jsonFile = File('${_appDocDirNewFolder.path}/Preferences.json');
jsonResult = mapInicial;
}
return jsonResult;
}
#override
void initState() {
loadJson();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Menu de Serviços',
debugShowCheckedModeBanner: false,
theme: androidTheme(),
home: ServiceList(jsonResult: jsonResult),
);
}
}

You marked loadJson as async so it completes after MaterialApp creation. Wrap MaterialApp with FutureBuilder, refactor loadJson to return value and set it as future prop. Then use snapshot.dataas input for ServiceList.
P.S. It is recommended to read more about asyncronicy. Read this for start https://www.woolha.com/articles/dart-event-loop-microtask-event-queue

Related

Async await does not wait for the output to return

I am making an bungalow reservation system with spring rest back end and flutter front end.
In this I want to get a list of bungalows.
So I decided to make a method to get the list of bungalows in a method using HttpService class that I made to handle the rest end points, That method is getBungalows() method.
Then I called this method by overriding initstate().
But the problem is that before my initstate() is completed. my build method starts.
To prove this I printed two lines 'print' and 'print build' as I thought I get 'print build' first. what am I doing wrong here. Please help.
Method to retrieve data from rest back end
When this happened I first checked this method but this works fine and return the desired result.
Future<List<Bungalow>> getBungalows() async {
Uri uri = Uri.parse('$url/bungalows/');
http.Response response = await http.get(uri);
if (response.statusCode == 200) {
List<Bungalow> bungalows = List<Bungalow>.from(
json.decode(response.body).map((x) => Bungalow.fromJson(x)));
// print(bungalows.first.address + 'asafafasfafdfgfgarfgargafvfrvaerg');
return bungalows;
} else {
throw 'Unable to retrieve data';
}
}
Code of the HomeScreen
class _HomeScreenState extends State<HomeScreen> {
HttpService httpService = HttpService();
late List<Bungalow> bungalows;
bool isLoggedIn = false;
User? user;
void getBungalows() async {
bungalows = await httpService.getBungalows();
print('done');
}
#override
Widget build(BuildContext context) {
if (widget.user != null) {
isLoggedIn = true;
user = widget.user;
}
print('done build');
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
Text(isLoggedIn ? user!.userDetail.username : 'No login'),
// TextButton(
// onPressed: () {
// setState(() {
// getBungalows();
// print(bungalows.first.address);
// });
// },
// child: Text('click'))
],
),
);
}
#override
void initState() {
getBungalows();
}
}
Console Output
I/flutter (22248): done build
I/flutter (22248): done
It is behaving correctly, initState function is not async and method getBungalows() is called in parallel.
You should either use setState in getBungalows, or add a listener, or use the then keyword, or use StreamBuilder.
Check this: https://stackoverflow.com/a/54647682/305135

Flutter - Pass data from a Future variable(sqlite) to a widget

I am very new to Flutter and Dart but I am trying to get data from a sqlite method and pass it as an argument to a new widget.
I've tried using FutureBuilder and it didn't help.
I am calling the method to load the data, wait for the data in .then() and inside then I set the state of a class variable to later be assigned to widget but the variable _records is always null even after setState.
Below is my code
int initScreen;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
initScreen = await prefs.getInt("initScreen");
await prefs.setInt("initScreen", 1);
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State{
List<Quarter> _records;
#override
void initState() {
_getRecords();
super.initState();
}
_getRecords() async {
await QuarterData().getQuarters().then((list){
setState((){
this. _records = list;
});
});
return list;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'UI',
debugShowCheckedModeBanner: false,
home: initScreen == 0 || initScreen == null
? SpashScreen(this._records)
: QuartersHome(this._records)
);
}
}
Many thanks
this._records is not initialised and will be null until your query is not done.
You need to check if it's not null before returning your widgets with it (like you do with initScreen)
If after the setState()method your variable is still null, maybe it's your query which return null.

how to use values from async functions in dart

I want to use string value in my code but I am unable to do so. Please help, I am new to flutter.
// Database (db) is database sqlite
// Dog class has a String field name
Text func() async{
var dog = await db.firstDog();
return Text(dog.name);
}
The return type Text isn't a Text, as defined by the method func.dart(return_of_invalid_type).
use Future
ForExample
Future<Text> func() async{
String d = await getTest();
return Text(d);
}
Prerequisites
Do tell us where this function call is placed (eg. in repository or screen layer)
What you can do
Instead of returning the Text as Widget, you can just return a Future<String>
Future<String> func() async {
var dog = await db.firstDog();
return dog.name;
}
Assuming that you are utilizing this on your screen or widget class directly, you can do this
Database db = new Database();
String _dogName;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(_dogName),
),
);
}
void func() async {
setState(() async {
var dog = await db.firstDog();
_dogName = dog.name;
});
}
Check this article on medium. You can get a good understanding.
For asynchronous functions you should use Future class for returning the values.
Future<String> asyncFunc() async {
d = await db.firstDog();
return d.name;
}
Then use this string to set the text for a TextView

How to assign value of Text Asset to String and display it in Text()?

I want to modify data from a text asset (.txt file) with a function and display it in a Text() widget. myFunction Takes datatype String as parameter and return datatype String.
I've read the documentation. Loading Asset from Image, this didn't worked. I've also tried solution from Suragch's Answer.
This maybe is a case where I should use FutureBuilder but I'm not able to understand how to make it work here (I'm new). I'm going to use another function to modify the data from file and then display.
This is one of the things I tried:
Widget build(BuildContext context) {
Future<String> aloadAsset(BuildContext context) async {
return await DefaultAssetBundle.of(context).loadString('assets/help.txt');
}
String helps = myFunction(await aloadAsset(context));
return Scaffold(
body: Text(helps)
);
}
When assigning value from await aloadAsset(context) to String, I get these errors: Unexpected text 'await'. & A value of type 'Future<String>' can't be assigned to a variable of type 'String'.
This is how to read the text from your file, you have to modify your Build function to include a FutureBuilder. Then you have to move the aloadAsset function out of the build:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: aloadAsset(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if(if(snapshot.hasData)){
return Scaffold(
body: Text(snapshot.data)
);
}
return Scaffold(
body: Text('No Available data') //This will be returned in case you didn't receive data yet or in the case of a file error.
);
}
}
Future<String> aloadAsset(BuildContext context) async {
return await DefaultAssetBundle.of(context).loadString('assets/help.txt');
}
await keyword can only be used in an async function. What you can do here is:
Make the widget as StatefulWidget and then in the state class:
String helps = "";
in the initState() method:
aloadAsset();
and change your function to:
aloadAsset() async {
helps = myFunction(await rootBundle.loadString('assets/help.txt'));
setState((){});
}
Don't forget to add the import
import 'package:flutter/services.dart' show rootBundle;

Flutter - setState not updating inner Stateful Widget

Basically I am trying to make an app whose content will be updated with an async function that takes information from a website, but when I do try to set the new state, it doesn't reload the new content. If I debug the app, it shows that the current content is the new one, but after "rebuilding" the whole widget, it doesn't show the new info.
Edit: loadData ( ) method, basically read a URL with http package, the URL contains a JSON file whose content changes every 5 minutes with new news. For example a .json file with sports real-time scoreboards whose scores are always changing, so the content should always change with new results.
class mainWidget extends StatefulWidget
{
State<StatefulWidget> createState() => new mainWidgetState();
}
class mainWidgetState extends State<mainWidget>
{
List<Widget> _data;
Timer timer;
Widget build(BuildContext context) {
return new ListView(
children: _data);
}
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
String s = await loadData();
this.setState(() {
_data = <Widget> [new childWidget(s)];
});
});
}
}
class childWidget extends StatefulWidget {
childWidget(String s){
_title = s;
}
Widget _title;
createState() => new childState();
}
class childState extends State<gameCardS> {
Widget _title;
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: foo(),
child: new Card(child: new Text(_title));
}
initState()
{
super.initState();
_title = widget._title;
}
}
This should sort your problem out. Basically you always want your Widgets created in your build method hierarchy.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(home: new Scaffold(body: new MainWidget())));
class MainWidget extends StatefulWidget {
#override
State createState() => new MainWidgetState();
}
class MainWidgetState extends State<MainWidget> {
List<ItemData> _data = new List();
Timer timer;
Widget build(BuildContext context) {
return new ListView(children: _data.map((item) => new ChildWidget(item)).toList());
}
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
ItemData data = await loadData();
this.setState(() {
_data = <ItemData>[data];
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
static int testCount = 0;
Future<ItemData> loadData() async {
testCount++;
return new ItemData("Testing #$testCount");
}
}
class ChildWidget extends StatefulWidget {
ItemData _data;
ChildWidget(ItemData data) {
_data = data;
}
#override
State<ChildWidget> createState() => new ChildState();
}
class ChildState extends State<ChildWidget> {
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: () => foo(),
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
child: new Card(
child: new Container(
padding: const EdgeInsets.all(8.0),
child: new Text(widget._data.title),
),
),
)
);
}
foo() {
print("Card Tapped: " + widget._data.toString());
}
}
class ItemData {
final String title;
ItemData(this.title);
#override
String toString() {
return 'ItemData{title: $title}';
}
}
This was really giving me headache and no Google results were working. What finally worked was so simple. In your child build() assign the value to the local variable before you return. Once I did this everything worked with subsequent data loads. I even took out the initState() code.
Many thanks to #Simon. Your answer somehow inspired me to try this.
In your childState:
#override
Widget build(BuildContext context) {
_title = widget._title; // <<< ADDING THIS HERE IS THE FIX
return new GestureDetector(onTap: foo(),
child: new Card(child: new Text(_title));
}
Hopefully this works in your code. For me, I use a Map for the entire JSON record passed in, rather than a single String, but that should still work.
The Root issue explained
initState(), for the child widget, is called only once when the Widget is inserted into the tree. Because of this, your child Widget variables will never be updated when they change on the parent widget. Technically the variables for the widgets are changing, you are just not capturing that change in your state class.
build() is the method that gets called every time something in the Widget changes. This is the reason #gregthegeek solution works. Updating the variables inside the build method of your child widget will ensure they get the latest from parent.
Works
class ChildState extends State<ChildWidget> {
late String _title;
#override
Widget build(BuildContext context) {
_title = widget._title; // <==== IMPORTANT LINE
return new GestureDetector(onTap: () => foo(),
child: new Text(_title),
);
}
}
Does not work
(It will not update when _title changes in parent)
class ChildState extends State<ChildWidget> {
late String _title;
#override
void initState() {
super.initState();
_title = widget._title; // <==== IMPORTANT LINE
}
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: () => foo(),
child: new Text(_title),
);
}
}
I'm unsure why this happens when calling setState(...) in an async function, but one simple solution is to use:
WidgetsBinding.instance.addPostFrameCallback((_) => setState(...));
instead of just setState(...)
This fixed my issue... If you have an initial value to be assigned on a variable use it in initState()
Note : Faced this issue when I tried to set initial value inside build function.
#override
void initState() {
count = widget.initialValue.length; // Initial value
super.initState();
}
don't use a future within a future; use different function that will return each future individually like this
List<Requests> requestsData;
List<DocumentSnapshot> requestsDocumentData;
var docId;
#override
void initState() {
super.initState();
getRequestDocs();
}
Future<FirebaseUser> getData() {
var _auth = FirebaseAuth.instance;
return _auth.currentUser();
}
getRequestDocs() {
getData().then((FirebaseUser user) {
this.setState(() {
docId = user.uid;
});
});
FireDb()
.getDocuments("vendorsrequests")
.then((List<DocumentSnapshot> documentSnapshots) {
this.setState(() {
requestsDocumentData = documentSnapshots;
});
});
for (DocumentSnapshot request in requestsDocumentData) {
this.setState(() {
requestsData.add(Requests(
request.documentID,
request.data['requests'],
Icons.data_usage,
request.data['requests'][0],
"location",
"payMessage",
"budget",
"tokensRequired",
"date"));
});
}
}
you can create individual functions for
FireDb().getDocuments("vendorsrequests")
.then((List<DocumentSnapshot> documentSnapshots) {
this.setState(() {
requestsDocumentData = documentSnapshots;
});
});
and
for (DocumentSnapshot request in requestsDocumentData) {
this.setState(() {
requestsData.add(Requests(
request.documentID,
request.data['requests'],
Icons.data_usage,
request.data['requests'][0],
"location",
"payMessage",
"budget",
"tokensRequired",
"date"));
});
}
I found that the use of
this
with setState is must
The real issue on child StatefulWidget not rebuilding is in the KEY
Hey, I'm a bit late to the discussion, but I think this is important.
I was facing a similar problem a while back and I even came to this thread to get some ideas.
In my case, I was simply getting widget.value directly inside the build method of the childWidget, and it was not updating when i called setState in the mainWidget.
Then i found this video: https://youtu.be/kn0EOS-ZiIc
(When to Use Keys - Flutter Widgets 101 Ep. 4) -
Here the Google dev talks about how keys in Flutter.
The short answer is
In a StatefulWidget the actual value you pass is stored in the state, not in the widget itself, like a StatelessWidget does.
When you call setState in the mainWidget, Flutter walks down the widget tree and checks each childWidget's type and key, to see if anything has changed. As stateful widgets store their values in the state, Flutter thinks the child widgets did not change (because the types and keys are the same) and does not rebuild them, even if the value changed.
The real solution is to give the widget a key containing the value that is changing, so when Flutter is walking down the tree, it notices that the key changed, and rebuilds the stateful widget.
Other solutions here may work as well, but if you want to really understand it, this video is worth watching.
first check whether it is a stateless or stateful widget,and if the class is stateless then make it to a stateful widget and try adding a code after closing the
setState(() { _myState = newValue; });
In my case, it was just defining the state as a class property and not a local variable in the build method
Doing this -
List<Task> tasks = [
Task('Buy milk'),
Task('Buy eggs'),
Task('Buy bread'),
];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) => TaskTile(
...
instead of this -
#override
Widget build(BuildContext context) {
List<Task> tasks = [
Task('Buy milk'),
Task('Buy eggs'),
Task('Buy bread'),
];
return ListView.builder(
itemBuilder: (context, index) => TaskTile(
...
Found the best solution.
If you are using a stateless widget you can't use set state, so just convert the stateless widget to statefull widget

Categories

Resources