I'm using geolocator plugin and getting current latitude and longitude but i can't load that in initstate of my Flutter Application.
It showing Render Error.
void initState() {
// TODO: implement initState
super.initState();
getCurrentLocation();
}
void getCurrentLocation() async {
var answer = await Geolocator().getCurrentPosition();
setState(() {
latitude = answer.latitude;
longitude = answer.longitude;
});
}
Map is Got updated with current location after some milli seconds but it showing these errors.
I/flutter (14143): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (14143): The following assertion was thrown building HomePage(dirty, state: _HomePageState#d55de):
I/flutter (14143): 'package:google_maps_flutter/src/location.dart': Failed assertion: line 17 pos 16: 'latitude !=
I/flutter (14143): null': is not true.
I/flutter (14143):
I/flutter (14143): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter (14143): more information in this error message to help you determine and fix the underlying cause.
I/flutter (14143): In either case, please report this assertion by filing a bug on GitHub:
I tried many ways until I found this way thanks to a kind person who helped on another flutter facebook group. Make sure in your pubspec.yaml you update location to the latest version
dependencies:
location: ^2.3.5
Then change it to the following code:
LocationData _currentLocation;
StreamSubscription<LocationData> _locationSubscription;
var _locationService = new Location();
String error;
void initState() {
super.initState();
initPlatformState();
_locationSubscription = _locationService
.onLocationChanged()
.listen((LocationData currentLocation) async {
setState(() {
_currentLocation = currentLocation;
});
});
}
void initPlatformState() async {
try {
_currentLocation = await _locationService.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
error = 'Permission denied';
}else if(e.code == "PERMISSION_DENIED_NEVER_ASK"){
error = 'Permission denied';
}
_currentLocation = null;
}
Run code snippetReturn to post
You may access longitude and latitude as
_currentLocation.longitude and _currentLocation.latitude
these will return double values. Also, there are more options available at https://pub.dev/packages/location#-readme-tab-
As Abbas.M suggestion i'm solving my problem using FutureBuilder Widget.
FutureBuilder Widget:
https://www.youtube.com/watch?v=ek8ZPdWj4Qo
I'm declaring variable _future
Future<Position> _future;
I'm calling my async method in the initState
void initState() {
// TODO: implement initState
super.initState();
_future = getCurrentLocation();
}
Using FutureBuilder widget i solved my problem and i'm passing my async function return value to parameter of FutureBuilder widget.
This Condition if(snapshot.connectionState == ConnectionState.done) helps to find our async function completed and returned value or not. if it is in Done state then it means function completed and returned.
If that condition is not satisfied then it means async function is not completed so i'm using CircularProgressIndicator widget to notify user to understand app is loading.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Krish"),
),
body: FutureBuilder(
future: _future,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (!snapshot.hasError) {
print(snapshot.data.latitude);
return Stack(children: <Widget>[
GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(
snapshot.data.latitude, snapshot.data.longitude),
zoom: 12.0),
onMapCreated: mapCreated,
),
Positioned(
top: 30.0,
left: 15.0,
right: 15.0,
child: Container(
height: 50.0,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.white),
child: TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter Address',
contentPadding:
EdgeInsets.only(top: 15.0, left: 15.0),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: searchAndNavigate,
iconSize: 30.0,
)),
onChanged: (value) {
searchAddress = value;
},
),
),
),
]);
}
} else {
return Center(child: CircularProgressIndicator());
}
}));
}
Future<Position> getCurrentLocation() async
{
var answer = await Geolocator().getCurrentPosition();
return answer;
}
I have almost no idea what's happening but based on the code since you have a .then, before the .then function happen your latitude and longitude are null, and when you have a .then the rest of your code is not awaiting for the future to be resolved. Try initializing the longitude and latitude to some value other than null in your init state so:
void initState() {
// TODO: implement initState
super.initState();
latitude = 0;
longitude = 0;
getCurrentLocation().then((k) {
latitude = k.latitude;
longitude = k.longitude;
setState(() {});
});
}
Related
I'm trying to access the JSON array data from an API and I am getting this error. I've tried replacing it with SnapShot.data()[index]['country'] and SnapShot.data.data()[index]['country'] and also with SnapShot[index]['country'] either of them not worked Please help me to figure it out
Error is : error: The operator '[]' isn't defined for the type 'Object'. (undefined_operator at [covid_tracker] lib\CountryWise.dart:66)
and API and JSON data which I am using is https://corona.lmao.ninja/v2/countries
// Here is initialization of 'datas' variable
final String url = "https://corona.lmao.ninja/v2/countries";
late Future <List> datas;
Future <List> getData() async{
var response = await Dio().get(url);
return response.data;
}
#override
void initState() {
// TODO: implement initState
super.initState();
datas=getData();
}
FutureBuilder(
future: datas,
builder:( BuildContext context, SnapShot){
if(SnapShot.hasData){
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 1.0,
),
itemBuilder: (BuildContext context, index) => SizedBox(
height: 50,
width: 50,
child: GestureDetector(
onTap: (){},
child: Card(
child: Container(
color: Colors.amberAccent,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Image(image: AssetImage("images/wdeath.png",),height: 85,width: 85,),
=======================> Text(SnapShot.data[index]['country'], //Error is here <============
style: TextStyle(color: Colors.white,fontSize: 18.0,fontWeight: FontWeight.bold),),
],
),
),
),
),
),
)
);
}
else{
return Center(child: CircularProgressIndicator(),);
}
},
),
enter image description here
Here is the picture and in line 66 the error is occurred.
I tried this method, I don't know how efficient it is but it worked.
I have created a separate class for the information I needed like this:
class CountryData{
final String country,cases,deaths,recovered,todayCases,todayDeaths,todayRecovered,flag;
CountryData(this.country, this.cases, this.deaths, this.recovered, this.todayCases, this.todayDeaths, this.todayRecovered, this.flag);
}
And I replaced this below code :
late Future <List> datas;
Future <List> getData() async{
var response = await Dio().get(url);
return response.data;
}
with this code
List<CountryData> countries = [];
Future getData() async{
var response = await http.get(Uri.parse(url));
var jsonData = jsonDecode(response.body);
for( var c in jsonData){
CountryData country = CountryData( c['country'],c['cases'].toString(),c['deaths'].toString(), c['recovered'].toString(),
c['todayCases'].toString(), c['todayDeaths'].toString(), c['todayRecovered'].toString(), c['countryInfo']['flag']);
countries.add(country);
print(country.country);
print(country.flag);
}
return countries;
}
And at the place where previously I was getting an error (mentioned in question), there I retrieved the data with the List of class which I defined earlier (List countries = [];) like this:
Image(image: NetworkImage(countries[index].flag),height: 85,width: 85,),
Text( countries[index].country,
style: TextStyle(color: Colors.white,fontSize: 18.0,fontWeight: FontWeight.bold),),
Don't know what is wrong with the method of retrieving the data with the snapshot earlier but this code worked for me.
Error:
location.dart failed assertion: .... 'latitude != null': is not true.
Apparently, the answer is here:
Flutter - Google Maps doesn´t wait to Location
However, none of these things worked for me.
Attempt:
show an empty Container() while lat and lng is null...
I have no clue what on earth this is... ==?? like with python?
lat == null || lng == null
? Container()
My guess is this guy wants me to assign lat and lng to null and put google map into a container. Here goes nothing:
var lat = null;
var lng = null;
I converted my sizedbox to a container and changed the initial camera position:
before:
SizedBox(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: _myLocation,
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
after:
Container(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: CameraPosition(
target: LatLng(lat, lng),
zoom: 15.0),
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
),
Result:
Failed assertion: 'latitude != null': is not true.
The program won't even compile now. to poke a bit, I changed the target to: target: LatLng(null, null),
Same error. Nothing has changed.
<wiped everything, started over>
"you can display a loader until you get your location"
Didn't work.
This is how I'm calling my longitude and latitude points from Google sheets. I'm trying to plot them on buttonpress:
Future<void> _plotCurrent() async {
Map<double,double> m = (await sheet.values.map.column(3, fromRow:2)).map((key, value)=>
MapEntry(double.parse(key), double.parse(value)));
Map<double,double> m2 = (await sheet.values.map.column(4, fromRow:2)).map((key, value)=>
MapEntry(double.parse(key), double.parse(value)));
Iterable _markers = Iterable.generate(10, (index) {
LatLng latLngMarker = LatLng(m["test$index"], m2["test$index"]);
return Marker(markerId: MarkerId("test$index"),position: latLngMarker);
});
setState(() {
markers = _markers;
});
}
I've read some stuff about having to change my Widget build tree into a Future type. However, I'm still terrible with Dart. I don't know to do it. Could this work? Here's the start of my Widget:
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title, style: TextStyle(fontSize: 11)),
centerTitle: true,
),
body:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
Please help. Thank you.
Edit:
This makes no sense.. Is geolocator messing this up or something? Here's what I have now:
#override
initState() {
super.initState();
getLocation();
}
var lng, lat;
Future getLocation() async {
Position position = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
setState(() {
lat = position.latitude;
lng = position.longitude;
print(lng);
});
}
#override
Widget build(BuildContext context) {
if (lat == null || lng == null) {
return Container();
}
Container(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: CameraPosition(
target: LatLng(lat, lng),
zoom: 15.0),
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
);
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title, style: TextStyle(fontSize: 11)),
centerTitle: true,
),
body:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Form(
key: _formKey,
child: Padding(
first problem is, my map isn't showing up anymore. I can print coordinates... So, I don't get why the error is still showing and everything is crashing after I press plot. Anybody?
Edit 2:
This may have something to do with my error:
The relevant error-causing widget was: MyHomePage
file:///C:/Users/xxx/Desktop/xxxxx/xxxx-xxx/lib/main.dart:92:13 When
the exception was thrown, this was the stack:
points to this widget:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'xx',
theme: ThemeData(
primarySwatch: colorCustom,
hintColor: Colors.white,
canvasColor: colorCustom,
backgroundColor: Colors.red,
),
home: MyHomePage(title: 'xx'),
);
}
}
I don't have enough experience with Dart to know what's wrong with having a homepage with a title separate from the rest of my widgets. Is this a bad thing?
I moved my widget elsewhere and applied the container. My app won't start with the lat == null || lng == null statement and throws the following error:
The following assertion was thrown building GoogleMap(state:
_GoogleMapState#81111): No Directionality widget found.
I think the problem is I have too much garbage loading in. I'm lost.
void main() => runApp(
RestartWidget(child: MyApp()),
);
class RestartWidget extends StatefulWidget {
RestartWidget({this.child});
final Widget child;
static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartWidgetState>().restartApp();
}
#override
_RestartWidgetState createState() => _RestartWidgetState();
}
class _RestartWidgetState extends State<RestartWidget> {
Key key = UniqueKey();
void restartApp() {
setState(() {
key = UniqueKey();
});
}
#override
Widget build(BuildContext context) {
return KeyedSubtree(
key: key,
child: widget.child,
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'xxxxxxx',
theme: ThemeData(
primarySwatch: colorCustom,
hintColor: Colors.white,
canvasColor: colorCustom,
backgroundColor: Colors.red,
),
home: MyHomePage(title: 'xxxxxxx'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
etc... I'm so lost in all of these Widgets. Something is causing my location to be null. I don't know. Anybody?
When dealing with async data loading, you need to have a placeholder container until your data gets loaded. In this case that's lat and long parameters.
Widget methodReturningWidget() {
// In case your data is not ready, return empty container
if (lat == null || long == null) {
return Container();
}
// In case your data is present, return GoogleMap object
return Container(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: CameraPosition(
target: LatLng(lat, long),
zoom: 15.0),
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
);
}
Part of the code that is crashing your app is this here LatLng(lat, long) that tries to create object, but it's parameters lat and long are null.
I don't have an approximate answer. However, I do believe I found the cause of this.
Map<double, double> m = (await sheet.values.map.column(3, fromRow: 2)).map((
key, value) =>
MapEntry(double.parse(key), double.parse(value)));
Map<double, double> m2 = (await sheet.values.map.column(4, fromRow: 2))
.map((key, value) =>
MapEntry(double.parse(key), double.parse(value)));
print(_markers);
setState(() {
Iterable _markers = Iterable.generate(10, (index) {
LatLng latLngMarker = LatLng(m["test$index"], m2["test$index"]);
return Marker(markerId: MarkerId("test$index"), position: latLngMarker);
});
markers = _markers;
In short, I'm calling the lat and long improperly somehow and it's throwing the error. I verified this by plotting a single point from my current location. Doing so worked without any issues. I'll have to research how to call my columns (coordinates) properly. If anyone has any insight, please let me know. Thanks
Edit: I think I found the problem.
my m and m2 are printing keys and values...!! The keys are messing everything up.
On my flutter application I am trying to show updating data. I am successful in getting data from weather api manually. But I need to constantly grab data every 5 seconds. So it should be updated automatically. Here is my code in Flutter :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sakarya Hava',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Sakarya Hava'),
),
body: Center(
child: FutureBuilder<SakaryaAir>(
future: getSakaryaAir(), //sets the getSakaryaAir method as the expected Future
builder: (context, snapshot) {
if (snapshot.hasData) { //checks if the response returns valid data
return Center(
child: Column(
children: <Widget>[
Text("${snapshot.data.temp}"), //displays the temperature
SizedBox(
height: 10.0,
),
Text(" - ${snapshot.data.humidity}"), //displays the humidity
],
),
);
} else if (snapshot.hasError) { //checks if the response throws an error
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
),
),
);
}
Future<SakaryaAir> getSakaryaAir() async {
String url = 'http://api.openweathermap.org/data/2.5/weather?id=740352&APPID=6ccf09034c9f8b587c47133a646f0e8a';
final response =
await http.get(url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
return SakaryaAir.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
}
I found such a snippet to benefit from :
// runs every 5 second
Timer.periodic(new Duration(seconds: 5), (timer) {
debugPrint(timer.tick);
});
Probably I need to wrap and call FutureBuilder with this snippet but I was not able to understand how to do it.
Futures can have 2 states: completed or uncompleted. Futures cannot "progress", but Streams can, so for your use case Streams make more sense.
You can use them like this:
Stream.periodic(Duration(seconds: 5)).asyncMap((i) => getSakaryaAir())
periodic emits empty events every 5 seconds and we use asyncMap to map that event into another stream, which get us the data.
Here is working example:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class ExamplePage extends StatelessWidget {
Future<String> getSakaryaAir() async {
String url =
'https://www.random.org/integers/?num=1&min=1&max=6&col=1&base=10&format=plain&rnd=new';
final response =
await http.get(url, headers: {"Accept": "application/json"});
return response.body;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Stream.periodic(Duration(seconds: 5))
.asyncMap((i) => getSakaryaAir()), // i is null here (check periodic docs)
builder: (context, snapshot) => Text(snapshot.data.toString()), // builder should also handle the case when data is not fetched yet
);
}
}
You can refactor your FutureBuilder to use a Future variable instead of calling the method in the FutureBuilder. This would require you to use a StatefulWidget and you can set up the future in your initState and update it by calling setState.
So you have a future variable field like:
Future< SakaryaAir> _future;
So your initState would look like this :
#override
void initState() {
super.initState();
setUpTimedFetch();
}
where setUpTimedFetch is defined as
setUpTimedFetch() {
Timer.periodic(Duration(milliseconds: 5000), (timer) {
setState(() {
_future = getSakaryaAir();
});
});
}
Finally, your FutureBuilder will be changed to:
FutureBuilder<SakaryaAir>(
future: _future,
builder: (context, snapshot) {
//Rest of your code
}),
Here is a DartPad demo: https://dartpad.dev/2f937d27a9fffd8f59ccf08221b82be3
I have a php file hosted on my college server and when i run this file on the server it works very well. I can get the json data after running my php file which is in the link http://www.alkadhum-col.edu.iq/Teachers%20Activities/get.php but when i was unable to got them when i had tried that in flutter on the app screen got "the getter 'length'was called on null".
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
runApp(Workshops());
}
class Workshops extends StatelessWidget {
#override
Widget build(BuildContext mycontext) {
return MaterialApp(
home:Scaffold(
appBar: AppBar(
backgroundColor: Color.fromRGBO( 52, 73, 94, 1.0),
automaticallyImplyLeading: false, // Don't show the leading button
title: new Text("PHP with Flutter"),
),
body: PostScreen(),
)
);
}
}
class PostScreen extends StatefulWidget {
#override
_PostScreenState createState() => _PostScreenState();
}
class _PostScreenState extends State<PostScreen> {
List<Post> _postList = new List<Post>();
Future<List<Post>> fetchPost() async {
final response =
await http.get('http://www.alkadhum-col.edu.iq/Teachers%20Activities/get.php');
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
List<dynamic> values = new List<dynamic>();
values = json.decode(response.body);
if (values.length > 0) {
for (int i = 0; i < values.length; i++) {
if (values[i] != null) {
Map<String, dynamic> map = values[i];
_postList.add(Post.fromJson(map));
}
}
}
return _postList;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Post>>(
future: fetchPost(),
builder: (_, AsyncSnapshot<List<Post>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
//dynamic post = snapshot.data[index];
return (Container(
margin: EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0),
child: new Card(
elevation: 10.0,
child: new Container(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Text(snapshot.data[index].name, style: TextStyle(fontSize: 18.0),),
new Text(snapshot.data[index].msg, style: TextStyle(fontSize: 18.0),),
new Text(snapshot.data[index].day, style: TextStyle(fontSize: 18.0),),
new Text(snapshot.data[index].date, style: TextStyle(fontSize: 18.0),),
],
),
),
),
));
},
);
},
);
}
#override
void initState() {
super.initState();
fetchPost();
}
}
class Post {
String name;
String msg;
String day;
String date;
Post({this.name, this.msg, this.day, this.date});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
name: json['name'],
msg: json['msg'],
day: json['day'],
date:json['date']
);
}
}
How to fix this issue?.
Thanks in advance.
I've looked at your link and tried running the code and I think the issue is in the data returned from your link.
[{"name":"م.م علي ستار باراني","msg":"امتحان مادة قواعد البيانات اول جابترين ","day":"السبت","date":"2019-06-20"}][{"name":"م. امجد عباس التميمي","msg":"امتحان مادة هندسة البرامجيات اول فصلين","day":"الاحد","date":"2019-06-21"},{"name":"م.م علي ستار باراني","msg":"امتحان مادة قواعد البيانات اول جابترين ","day":"السبت","date":"2019-06-20"}]
Right after the first object, you have a closing square bracket and no comma separating it from the opening square bracket beside it. Calling json.decode() on the link body throws the following error
FormatException (FormatException: Unexpected character (at character 115)
...,"day":"السبت","date":"2019-06-20"}][{"name":"م. امجد عباس التميمي","msg...
^
)
After fixing that, it runs fine for me. I tested by taking the body of the link manually and removing the offending characters, leaving me with the json below.
[{"name":"م.م علي ستار باراني","msg":"امتحان مادة قواعد البيانات اول جابترين ","day":"السبت","date":"2019-06-20"},{"name":"م. امجد عباس التميمي","msg":"امتحان مادة هندسة البرامجيات اول فصلين","day":"الاحد","date":"2019-06-21"},{"name":"م.م علي ستار باراني","msg":"امتحان مادة قواعد البيانات اول جابترين ","day":"السبت","date":"2019-06-20"}]
Running the app now displays the following:
make sure to always check for nulls in itemCount like this
ListView.builder(
itemCount: snapshot.data.length == null ? 0 :snapshot.data.length,
itemBuilder: (_, index){}
),
With this if your list is null, the itemCount will return a 0.
Just do this to the list List<Post> _postList = [] that happens its that everything it a object in Dart so when you do this List<Post> _postList = new List<Post>(); your variable _postList is equal to null because has been declared but not initialize so by default it null and you will not be able to use any property for the list until it initialize.
in resume just initialize your list like this: an empty list
List<Post> _postList = [];
So you don't have that issue.
I am writing an application to connect to Proxmox in Flutter, and I need to get the various Authentication Realms. The issue I have had is that most servers are using a self-signed SSL certificate and the http import does not support that. This has forced me to use the dart:io package and its HttpClient. However using this method does not return any results, the List is null.
D/ ( 9335): HostConnection::get() New Host Connection established 0xe047c540, tid 9354
D/EGL_emulation( 9335): eglMakeCurrent: 0xe76a7ac0: ver 3 0 (tinfo 0xccd07000)
I/flutter ( 9335): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 9335): The following NoSuchMethodError was thrown building FormField<dynamic>(dirty, state:
I/flutter ( 9335): FormFieldState<dynamic>#11694):
I/flutter ( 9335): The method 'map' was called on null.
I/flutter ( 9335): Receiver: null
I/flutter ( 9335): Tried calling: map<DropdownMenuItem<String>>(Closure: (AuthRealm) => DropdownMenuItem<String>)
This is my client class:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:Proxcontrol/Client/Objects/auth_realms.dart';
class Client {
String baseUrl;
Client(String url, String port) {
baseUrl = "https://" + url + ":" + port + "/api2/json/";
}
Future<List<AuthRealm>> getAuthRealms() async {
HttpClient client = new HttpClient();
client.badCertificateCallback =((X509Certificate cert, String host, int port) => true);
var request = await client.getUrl(Uri.parse(baseUrl + "access/domains"));
var response = await request.close();
return await response.transform(Utf8Decoder()).transform(JsonDecoder()).map((json) => AuthRealm.fromJson(json)).toList();
}
}
This is my AuthRealm object class that the request is mapped to:
class AuthRealm {
final String type;
final String realm;
final String comment;
AuthRealm({this.type, this.realm, this.comment});
factory AuthRealm.fromJson(Map<String, dynamic> json) {
return AuthRealm(
type: json['type'],
realm: json['realm'],
comment: json['comment']
);
}
}
And this is where I am trying to get the Authentication Realms. It then passes them to a new page where they are displayed in a dropdownbutton. The serverAddress and serverPort fields are populated via TextFields.
final nextButton = RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24)),
onPressed: () {
Client client = new Client(serverAddress, serverPort);
client.getAuthRealms().then((values) {
realms = values;
});
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ServerAuthLoginScreen(authRealms: realms)));
},
padding: EdgeInsets.all(10),
color: Colors.indigoAccent,
child: Text('NEXT', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
);
And finally the dropdownbutton section that is populated with the Authentication Realms upon loading that screen.
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:Proxcontrol/Client/Objects/auth_realms.dart';
class ServerAuthLoginScreen extends StatefulWidget {
final List<AuthRealm> authRealms;
const ServerAuthLoginScreen({Key key, #required this.authRealms}) : super(key: key);
#override
_ServerAuthLoginScreenState createState() => _ServerAuthLoginScreenState(authRealms);
}
class _ServerAuthLoginScreenState extends State<ServerAuthLoginScreen> {
List<AuthRealm> authRealms;
_ServerAuthLoginScreenState(this.authRealms);
String serverRealm;
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
final realmSelector = FormField(
builder: (FormFieldState state) {
return InputDecorator(
decoration: InputDecoration(
icon: const Icon(FontAwesomeIcons.server),
labelText: 'Select an Auth Realm'),
isEmpty: serverRealm == '',
child: new DropdownButtonHideUnderline(
child: new DropdownButton(
isDense: true,
items: authRealms.map((AuthRealm value) {
return new DropdownMenuItem(
value: value.realm,
child: Text(value.realm),
);
}).toList(),
onChanged: (String value) {
setState(() {
serverRealm = value;
state.didChange(value);
});
}
)
),
);
},
);
_buildVerticalLayout() {
return ListView(
shrinkWrap: true,
children: <Widget>[
Padding(
padding: EdgeInsets.only(
left: screenWidth / 12,
right: screenWidth / 12,
top: screenHeight / 30),
child: realmSelector,
),
],
);
}
return Scaffold(
appBar: AppBar(
title: Text('Server Connection Details'),
centerTitle: true),
body: _buildVerticalLayout()
);
}
}
This is what my test proxmox server gives as a result to the GET request at the defined address:
{
"data":[
{
"type":"ad",
"realm":"CELESTIALDATA"
},
{
"type":"pam",
"comment":"Linux PAM standard authentication",
"realm":"pam"
},
{
"type":"pve",
"comment":"Proxmox VE authentication server",
"realm":"pve"
}
]
}
Can someone please help me understand what is going wrong? FYI I just started working with Dart/Flutter a few days ago so I am still learning how things function here. I come from a Java/C++/Python background.
UPDATE:
I modified my client in response to Richard's comment:
Future<List<AuthRealm>> getAuthRealms() async {
HttpClient client = new HttpClient();
client.badCertificateCallback =((X509Certificate cert, String host, int port) => true);
http.IOClient ioClient = new http.IOClient(client);
final response = await ioClient.get(baseUrl + "access/domains");
print(response.body);
final data = json.decode(response.body);
List<AuthRealm> realms = data.map((j) => AuthRealm.fromJson(j)).toList();
return realms;
}
However I am still getting an error and everything I am seeing just is not working.
I/flutter (12950): {"data":[{"type":"ad","realm":"CELESTIALDATA"},{"type":"pve","comment":"Proxmox VE authentication server","realm":"pve"},{"realm":"pam","comment":"Linux PAM standard authentication","type":"pam"}]}
E/flutter (12950): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: type '(dynamic) => AuthRealm' is not a subtype of type '(String, dynamic) => MapEntry<dynamic, dynamic>' of 'transform'
E/flutter (12950): #0 Client.getAuthRealms (package:Proxcontrol/Client/client.dart:70:35)
E/flutter (12950): <asynchronous suspension>
data is a Map, so you need to access the element in that map that's the list of realms. Use data['data'] to reference that list.
To convert that list of decoded json bits (List<Map<String, dynamic>>) to a list of AuthRealm use .map<AuthRealm>((j) => [something that constructs an AuthRealm]).toList()
This should work:
final data = json.decode(response.body);
List<AuthRealm> realms = data['data'].map<AuthRealm>((j) => AuthRealm.fromJson(j)).toList();
May be you should use setState like this
client.getAuthRealms().then((values) {
setState((){
realms = values;
});
});
in your code
final nextButton = RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24)),
onPressed: () {
Client client = new Client(serverAddress, serverPort);
client.getAuthRealms().then((values) {
setState(() {
realms = values;
});
});
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ServerAuthLoginScreen(authRealms: realms)));
},
padding: EdgeInsets.all(10),
color: Colors.indigoAccent,
child: Text('NEXT', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
);