This is a small POC in Flutter, where my build() function is being called again and again.
This was not expected at all without any loops and after a lot of research, I am calling "Future" in initState() as well.
But still facing the same issue.
Thank you in advance for time!
What have I tried..
import 'package:flutter/material.dart';
//http_request
import 'package:http/http.dart' as http; //to handle the http request
// import 'dart:async'; // for async functions
import 'dart:async' show Future;
import 'dart:convert'; //to convert the http response in JSON formate
import 'HomePage.dart';
class Reports extends StatefulWidget {
#override
_Reports createState() => _Reports();
}
class _Reports extends State<Reports> {
static String url = "Some Url";
String _response = "abc";
#override
void initState() {
super.initState();
getTradeName_dropdown_ITR_Computation_DATA();
}
#override
Widget build(BuildContext context) {
print('body');
return Scaffold(
body: Container(
child: new Text(_response),
),
);
}
Future getTradeName_dropdown_ITR_Computation_DATA() async {
try {
http.Response response =
await http.get("http://" + url );
if (this.mounted) {
setState(() {
String jsonTradeName_dropdown = response.body;
_response = jsonTradeName_dropdown;
});
}
} on Exception {
setState(() {
_response = "Some error occored. Please Try again...";
});
}
}
}
output:
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
I/flutter ( 5760): body
You were making couple of mistakes, here is the correct code. You should use a String instead of a Text widget to show the response.
class _Reports extends State<Reports> {
static String url = "url";
String _response = "abc";
#override
void initState() {
super.initState();
getTradeName_dropdown_ITR_Computation_DATA();
}
#override
Widget build(BuildContext context) {
print('body');
return Scaffold(
body: Container(
child: new Text(_response),
),
);
}
Future getTradeName_dropdown_ITR_Computation_DATA() async {
try {
http.Response response =
await http.get("url_goes_here");
if (this.mounted) {
setState(() {
String jsonTradeName_dropdown = response.body;
_response = jsonTradeName_dropdown;
});
}
} on Exception {
setState(() {
_response = "Some error occored. Please Try again...";
});
}
}
}
The proper model for understanding build() is that you should imagine it is being called sixty times a second. So your build() routine should be fast and idempotent.
In practice, there are optimizations made by the framework so that it isn't called except when needed, but you shouldn't view excessive calls to build() as a failure.
Related
This error does not occur on Android or web but only on IOS. It seem very trivial but I can't figure out what's wrong.
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'dart:convert';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScanQrPage extends StatefulWidget {
#override
_ScanQrPageState createState() => _ScanQrPageState();
}
class _ScanQrPageState extends State<ScanQrPage> {
final qrKey = GlobalKey();
late QRViewController qrViewController;
late Barcode barcode;
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
qrViewController.pauseCamera();
} else if (Platform.isIOS) {
qrViewController.resumeCamera();
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
Navigator.of(context).pop("");
return new Future(() => true);
},
child: Scaffold(
body: Stack(
children: [
buildQrView(context),
],
),
),
);
}
Widget buildQrView(BuildContext context) {
return QRView(
onQRViewCreated: onQRViewCreated,
key: qrKey,
overlay: QrScannerOverlayShape(
cutOutSize: MediaQuery.of(context).size.width * 0.8),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
qrViewController.dispose();
super.dispose();
}
void onQRViewCreated(QRViewController qrViewController) {
setState(() {
this.qrViewController = qrViewController;
});
qrViewController.scannedDataStream.listen((event) {
setState(() {
this.barcode = event;
if (Platform.isAndroid) {
qrViewController.pauseCamera();
} else if (Platform.isIOS) {
qrViewController.resumeCamera();
}
String rawData = event.code;
Uri data = Uri.dataFromString(rawData);
String para1 = data.queryParameters["buy"] ??
""; //get parameter with attribute "para1"
Codec<String, String> stringToBase64 = utf8.fuse(base64);
if (para1 != "") {
placer = stringToBase64.decode(para1);
}
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
Navigator.pop(context, placer);
});
});
});
}
}
I've tried all the solutions with the same error found on stackoverflow (addPostFrameCallback and Future(Duration.zero)) but none of them are exactly the same and does not seem to fix my problem.
I don't think I have having the same issue as any of the other questions.
The exception is happening on the Navigator.pop(context, placer);
Does anyone have any idea how to overcome this?
Why does this only happen on IOS?
I am using flutter scoped_model, When we try to access ScopedModel from child Widget which able to access without error.
But same code was not working with when i try to access it from Widget which load using Navigator.push, it gives error Error: Could not find the correct ScopedModel.
PageModel declare at top page.
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class PageModel extends Model {
String title;
static PageModel of(BuildContext context) =>
ScopedModel.of<PageModel>(context);
loadTitle() {
title = 'Old Title ';
}
updateTitle() {
title = 'New Title';
notifyListeners();
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final PageModel model = PageModel();
#override
Widget build(BuildContext context) {
return ScopedModel<PageModel>(
model: model,
child: Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: HomePageBody(),
),
);
}
}
class HomePageBody extends StatefulWidget {
#override
_HomePageBodyState createState() => _HomePageBodyState();
}
class _HomePageBodyState extends State<HomePageBody> {
#override
void initState() {
PageModel.of(context).loadTitle();
super.initState();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<PageModel>(
builder: (BuildContext context, child, PageModel model) {
return Column(
children: <Widget>[
Text(model.title),
RaisedButton(
child: Text('Edit'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(), fullscreenDialog: true),
);
},
),
],
);
});
}
}
class DetailPage extends StatefulWidget {
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Page'),
),
body: RaisedButton(
child: Text('Update'),
onPressed: () {
PageModel.of(context).updateTitle();
},
),
);
}
}
From DetailPage when we call PageModel.of(context).updateTitle(); following error coming,
/flutter (26710): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter (26710): The following ScopedModelError was thrown while handling a gesture:
I/flutter (26710): Error: Could not find the correct ScopedModel.
I/flutter (26710):
I/flutter (26710): To fix, please:
I/flutter (26710):
I/flutter (26710): * Provide types to ScopedModel<MyModel>
I/flutter (26710): * Provide types to ScopedModelDescendant<MyModel>
I/flutter (26710): * Provide types to ScopedModel.of<MyModel>()
I/flutter (26710): * Always use package imports. Ex: `import 'package:my_app/my_model.dart';
I/flutter (26710):
I/flutter (26710): If none of these solutions work, please file a bug at:
I/flutter (26710): https://github.com/brianegan/scoped_model/issues/new
What you’re doing is trying to find a PageModel of that context which there is none, since you haven’t created any for that specific widget context.
What you want to wrap your RaisedButton in a ScopedModelDescendant<PageModel> and update your model by using the model.updateTitle() instead.
That will look for the closest PageModel ancestor in the tree.
I am trying to fetch the data from json api and to be listed in the drop down list but i am hitting with The method 'map' was called on null error.
import "package:flutter/material.dart";
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(MaterialApp(
title: "Hospital Management",
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _mySelection;
final String url = "http://webmyls.com/php/getdata.php";
List data;
Future<String> getSWData() async {
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
var resBody = json.decode(res.body);
setState(() {
data = resBody;
});
print(resBody);
return "Sucess";
}
#override
void initState() {
super.initState();
this.getSWData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Hospital Management"),
),
body: new Center(
child: new DropdownButton(
items: data.map((item) {
return new DropdownMenuItem(
child: new Text(item['item_name']),
value: item['id'].toString(),
);
}).toList(),
onChanged: (newVal) {
setState(() {
_mySelection = newVal;
});
},
value: _mySelection,
),
),
);
}
}
The error from the debug consol
flutter: The method 'map' was called on null.
flutter: Receiver: null
flutter: Tried calling: map<DropdownMenuItem<String>>(Closure:
(dynamic) => DropdownMenuItem<String>)
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 Object.noSuchMethod
(dart:core/runtime/libobject_patch.dart:48:5)
I am expecting to show the list of data from the json api in the drop down menu. I am beginner to dart and flutter. Help me out to solve the error.
data is not initialized. It is set on getSWData which is async.
A possible solution could be to have data as emptyList as an initial value.
List data = List();
If you want to display DropdownButton based on data, you can use FutureBuilder and show loader or something till data comes from getSWData
Your Api http://webmyls.com/php/getdata.php returns a list not a map. Hence, it cannot have direct property advisor_report. Consider removing it.
Data should be changed inside setState.
You never called getSWData()
Below is more improved example.
import "package:flutter/material.dart";
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(MaterialApp(
title: "Hospital Management",
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _mySelection;
final String url = "http://webmyls.com/php/getdata.php";
List data;
Future<String> getSWData() async {
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
var resBody = json.decode(res.body);
setState(() {
data = resBody;
});
print(resBody);
return "Sucess";
}
#override
void initState() {
super.initState();
this.getSWData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Hospital Management"),
),
body: new Center(
child: new DropdownButton(
items: data.map((item) {
return new DropdownMenuItem(
child: new Text(item['item_name']),
value: item['id'].toString(),
);
}).toList(),
onChanged: (newVal) {
setState(() {
_mySelection = newVal;
});
},
value: _mySelection,
),
),
);
}
}
Yesterday I made a simple widget that fetches data to display some basic info, but I noticed that when I pop back to the list, the data is usually absent and I only get my error texts.
I figured this is due to these widgets originally being stateless, so I'm trying to convert them to stateful in order to reload the data when the page is loaded.
This is how I gather the data for my widget:
class BasicDogWidget extends StatefulWidget {
String URL;
BasicDogWidget(this.URL);
#override
createState() => new BasicDogWidgetState(URL);
}
class BasicDogWidgetState extends State<BasicDogWidget> {
String URL;
BasicDogWidgetState(this.URL);
var result;
var imageLink;
var dogName;
var dogType;
var dogColor;
var dogGender;
var dogAge;
#override
initState() {
fetchImageLink(URL).then((result) {
setState(imageLink = result);
});
fetchDogInfo(URL, 'datas-nev').then((result) {
setState(dogName = result);
});
fetchDogInfo(URL, 'datas-tipus').then((result) {
setState(dogType = result);
});
fetchDogInfo(URL, 'datas-szin').then((result) {
setState(dogColor = result);
});
fetchDogInfo(URL, 'datas-nem').then((result) {
setState(dogGender = result);
});
fetchDogInfo(URL, 'datas-kor').then((result) {
setState(dogAge = result);
});
super.initState();
}
#override
Widget build(BuildContext context) {
if (imageLink == null) {
return new Container();
}
if (dogName == null) {
return new Container();
}
if (dogType == null) {
return new Container();
}
if (dogColor == null) {
return new Container();
}
if (dogGender == null) {
return new Container();
}
if (dogAge == null) {
return new Container();
}
return buildBasicWidget(
imageLink, dogName, dogType, dogColor, dogGender, dogAge, URL);
}
}
However, it seems that the data collected by fetchDogInfo can't be passed in the setState method as it is a string.
E/flutter (12296): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter (12296): type 'String' is not a subtype of type 'VoidCallback' of 'fn' where
E/flutter (12296): String is from dart:core
E/flutter (12296):
E/flutter (12296): #0 State.setState (package:flutter/src/widgets/framework.dart:1086)
E/flutter (12296): #1 BasicDogWidgetState.initState.<anonymous closure> (package:osszefogasaszanhuzokert/dog.dart:230)
Is there any way this issue can be bypassed?
You are executing async code
fetchImageLink(URL).then((result) {
which means fetchImageLink(URL) will eventually return a value and then then(...) is executed, but this call is async, which means it's added to the event queue for later execution and the code synchronically continues to execute until the end of initState and then build until this sync code is run to its completion, then the next "task" from the event queue is executed, which might be the then(...) part from your fetchImageLink() call if it already completed.
That shouldn't be a problem though.
You could just check if the value is available
#override
Widget build(BuildContext context) {
if(imageLink == null) {
return new Container(); // dummy widget until there is something real to render
}
... // your other build code
Flutter have FutureBuilder class. It provides a widget that does all the heavy lifting to support asynchronous Future objects. The nice thing about FutureBuilder is that is can be added directly to the widget tree, and based off the current status of the connection, the relevant UI can be displayed to indicate progress, a result or an error.
Example:
class JokePageState extends State<JokePage> {
Future<String> response;
initState() {
super.initState();
response = http.read(dadJokeApi, headers: httpHeaders);
}
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new FutureBuilder<String>(
future: response,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Icon(Icons.sync_problem);
case ConnectionState.waiting:
case ConnectionState.active:
return const CircularProgressIndicator();
case ConnectionState.done:
final decoded = json.decode(snapshot.data);
if (decoded['status'] == 200) {
return new Text(decoded['joke']);
} else {
return const Icon(Icons.error);
}
}
},
),
),
);
}
}
In my app, I have a drawer with a UserAccountsDrawerHeader, which I feed its properties by simply getting the x property from FirebaseAuth.instance.currentUser.x
In the latest firebase_auth 0.2.0 version , where currentUser() is async.
I have been trying for several hours to store the information of the currently logged user and have not yet reached the correct way to do this.
I understand that I can access them by something like the following:
Future<String> _getCurrentUserName() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user.displayName;
}
...
new UserAccountsDrawerHeader(accountName: new Text(_getCurrentUserName()))
I understand that these code snippets will give type mismatch, but I am just trying to illustrate what I am trying to do.
What am I missing exactly that is preventing me from reaching a solution?
Update
class _MyTabsState extends State<MyTabs> with TickerProviderStateMixin {
TabController controller;
Pages _page;
String _currentUserName;
String _currentUserEmail;
String _currentUserPhoto;
#override
void initState() {
super.initState();
_states();
controller = new TabController(length: 5, vsync: this);
controller.addListener(_select);
_page = pages[0];
}
My method
I just coupled the auth state with my previously implemented TabBar state
_states() async{
var user = await FirebaseAuth.instance.currentUser();
var name = user.displayName;
var email = user.email;
var photoUrl = user.photoUrl;
setState(() {
this._currentUserName=name;
this._currentUserEmail=email;
this._currentUserPhoto=photoUrl;
_page = pages[controller.index];
});
}
My Drawer
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new UserAccountsDrawerHeader(accountName: new Text(_currentUserName) ,
accountEmail: new Text (_currentUserEmail),
currentAccountPicture: new CircleAvatar(
backgroundImage: new NetworkImage(_currentUserPhoto),
),
Here is the exception I get from the debug console
I/flutter (14926): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (14926): The following assertion was thrown building MyTabs(dirty, state: _MyTabsState#f49aa(tickers:
I/flutter (14926): tracking 1 ticker)):
I/flutter (14926): 'package:flutter/src/widgets/text.dart': Failed assertion: line 207 pos 15: 'data != null': is not
I/flutter (14926): true.
I/flutter (14926): Either the assertion indicates an error in the framework itself, or we should provide substantially
Update 2:
This is how I modified the google sign in function from the firebase examples:
Future <FirebaseUser> _testSignInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
//checking if there is a current user
var check = await FirebaseAuth.instance.currentUser();
if (check!=null){
final FirebaseUser user = check;
return user;
}
else{
final FirebaseUser user = await _auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getToken() != null);
return user;
}
}
Update 3:
My main function
void main() {
runApp(
new MaterialApp(
home: new SignIn(),
routes: <String, WidgetBuilder>{
"/SignUp":(BuildContext context)=> new SignUp(),
"/Login": (BuildContext context)=> new SignIn(),
"/MyTabs": (BuildContext context)=> new MyTabs()},
));
}
And then my SignIn contains a google button that when pressed:
onPressed: () { _testSignInWithGoogle(). //async returns FirebaseUser
whenComplete(()=>Navigator.of(context).pushNamed("/MyTabs")
);
}
and the Drawer from update 1 is included within MyTabs build.
There are several possibilities.
First : Use a stateful widget
Override the initState method like this :
class Test extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
String _currentUserName;
#override
initState() {
super.initState();
doAsyncStuff();
}
doAsyncStuff() async {
var name = await _getCurrentUserName();
setState(() {
this._currentUserName = name;
});
}
#override
Widget build(BuildContext context) {
if (_currentUserName == null)
return new Container();
return new Text(_currentUserName);
}
}
Second : Use the FutureBuilder widget
Basically, it's a wrapper for those who don't want to use a stateful widget. It does the same in the end.
But you won't be able to reuse your future somewhere else.
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future: _getCurrentUserName(),
builder: (context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData)
return new Text(snapshot.data.toString());
else
return new Container();
},
);
}
}
Explanation :
Your getCurrentUserName is asynchronous.
You can't just directly mix it with other synchronous functions.
Asynchronous functions are quite useful. But if you want to use them, just remember two things :
Inside another async function, you can var x = await myFuture, which will wait until myFuture finish to get it's result.
But you can't use await inside a sync function.
Instead, you can use
myFuture.then(myFunction) or myFuture.whenComplete(myFunction). myFunction will be called when the future is finished. And they both .then and .whenComplete will pass the result of your future as parameter to your myFunction.
"How to properly implement authentification" ?
You should definitely not do it this way. You'll have tons of code duplication.
The most ideal way to organise layers such as Authentification is like this :
runApp(new Configuration.fromFile("confs.json",
child: new Authentification(
child: new MaterialApp(
home: new Column(
children: <Widget>[
new Text("Hello"),
new AuthentifiedBuilder(
inRoles: [UserRole.admin],
builder: (context, user) {
return new Text(user.name);
}
),
],
),
),
),
));
And then, when you need a configuration or the current user inside a widget, you'd do this :
#override
Widget build(BuildContext context) {
var user = Authentification.of(context).user;
var host = Configuration.of(context).host;
// do stuff with host and the user
return new Container();
}
There are so many advantages about doing this, that there's no reason not to do it.
Such as "Code once, use everywhere". Or the ability to have a generic value and override it for a specific widget.
You'll realise that a lot of Flutter widgets are following this idea.
Such as Navigator, Scaffold, Theme, ...
But "How to do this ??"
It's all thanks to the BuildContext context parameter. Which provides a few helpers to do it.
For exemple, the code of Authentification.of(context) would be the following :
class Authentification extends StatefulWidget {
final Widget child;
static AuthentificationData of(BuildContext context) {
final AuthentificationData auth = context.inheritFromWidgetOfExactType(AuthentificationData);
assert(auth != null);
return auth;
}
Authentification({this.child});
#override
AuthentificationState createState() => new AuthentificationState();
}