Flutter BottomSheet callback context with Provider - google-maps

I'm having issues with the GoogleMap flutter plugin, when closing a showModalBottomSheet, getting the correct callback with correct context.
When my map is created, onMapCreated() is called, and then FAB_DrawerMarkers() is run. This gets a list of objects from the SQFlite DB via Provider, converts them to markers, then draws them, when the app is first run. I have a FAB which opens a showModalBottomSheet, which is used as a settings dialog (covering half the screen). When I tap satellite or normal, I can change the GoogleMap MapType and setState() will make sure it redraws the map instantly. I can also change the returned list of objects, filtered, etc.
When I close the showModalBottomSheet (or even when the selection is made), I want the Map to requery the DB and redraw the markers. However, I'm getting a context error where the Provider cannot be found. I've tried showModalBottomSheet .whenComplete(() => {}) and .then((value) {}) to call some form of callback. However, when I try to call _FAB_DrawMarkers(); I find that the widget has not yet been mounted, and so no Provider can be found.
I've given some examples of the state of flow of the variables with print() below. So I'd like:
To work out how to correctly callback after making a setting change, to force the map to requery the DB (via Provider) using correct context or timing.
If I can do this without any callback (eg trigger the drawmarkers on setting change) that is fine.
I've tried to keep things tidy by putting them in separate dart files, and want to make sure this is correctly done.
MAIN.dart >> SectionMain.dart >> SectionSettings - (main app state passed down to children to be able to call setState() )
Main.dart:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyAppStateContainer>(
create: (context) => MyAppStateContainer(),
child: MaterialApp(
home: Builder(
builder: (context) => SectionMain(mainAppStateRef: this)
MyAppStateContainer.dart:
class MyAppStateContainer extends ChangeNotifier {
MyAppStateContainer();
MapType _mapType = MapType.terrain;
String _someValue = "ABC";
String get getSomeValue => _someValue;
}
SectionMain.dart:
SectionSettings sectionSettings = SectionSettings();
class SectionMain extends StatefulWidget {
const SectionMain({Key key, #required this.mainAppStateRef}) : super(key: key);
final State mainAppStateRef;
#override
_SectionMain createState() => _SectionMain();
}
class _SectionMain extends State<SectionMain> {
BuildContext thisMapPageContext;
final Set<Marker> _markers = {};
GoogleMapController mapController;
MapMarkers mapMarkers = MapMarkers();
...
void callbackTest() {
print("************************ callback again");
_FAB_DrawMarkers();
}
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
//Load custom marker images
mapMarkers.init(); //load custom marker images
//trigger draw markers
_FAB_DrawMarkers();
}
#override
Widget build(BuildContext context) {
this.thisMapPageContext = context;
return Scaffold(
appBar: AppBar(
title: TextMy Map'),
backgroundColor: Colors.green[700],
),
//Put in a stack widget so can layer other widgets on top of map widget
body: Stack(
children: <Widget>[
//Builder so we can get a CTX made, and then Provider.of() finds the Provider
Builder(
builder: (context) => GoogleMap(
mapType: Provider.of<MyAppStateContainer>(context).getMapType, <--- works fine
minMaxZoomPreference: MinMaxZoomPreference(5,8),
markers: _markers,
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _center,
zoom: 6, //11.0,
),
mapToolbarEnabled: false,
myLocationEnabled: true,
onTap: (LatLng location) {
setState(() {
...
});
},
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.bottomCenter,
child:
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
Builder(
builder: (context) =>
FloatingActionButton(
child: Icon(Icons.settings, size: 36.0),
backgroundColor: Colors.green,
onPressed: () {
sectionSettings.onSheetShowContents(widget.mainAppStateRef, context);
}),
),
...
Builder(
builder: (context) =>
FloatingActionButton(
onPressed: () {
_FAB_DrawMarkers();
},
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.green,
child: const Icon(Icons.pin_drop, size: 36.0),
),
),
...
])),
),
],
),
);
}
void _FAB_DrawMarkers() async {
BuildContext getContext = this.thisMapPageContext;
print("############ PROBLEM HERE:");
print(getContext);
print(this.widget);
print(this);
print(this.mounted);
print("############");
final selectedValue = Provider.of<MyAppStateContainer>(getContext, listen:false).getSomeValue; <--- works first time
db_manager.DatabaseHelper helper = db_manager.DatabaseHelper.instance;
...
}
}
void settingsClosedCallBack() {
print("CallBack");
_SectionMain().callbackTest();
}
SectionSettings.dart:
class SectionSettings {
State MainState;
Future<dynamic> onSheetShowContents(State mainAppState, BuildContext context) {
MainState = mainAppState;
return showModalBottomSheet(
//showBottomSheet(
context: context,
builder: (context) {
return ListView(
padding: EdgeInsets.all(15.0),
children: <Widget>[
ListTile(
title: Text("Map Settings"),
selected: true,
),
...
ChoiceChip(
label: Text("Normal Map"),
selected: Provider.of<MyAppStateContainer>(context,listen: false).getMapType == MapType.terrain,
onSelected: (value) {
Provider.of<MyAppStateContainer>(context,listen: false).setMapType(MapType.terrain);
MainState.setState(() {
});
},
),
ChoiceChip(
label: Text("Satellite Map"),
selected: Provider.of<MyAppStateContainer>(context,listen: false).getMapType == MapType.satellite,
onSelected: (value) {
Provider.of<MyAppStateContainer>(context, listen: false).setMapType(MapType.satellite);
MainState.setState(() {
});
},
),
...
],
);
//}).whenComplete(() => {
}).then((value) {
//TODO Callback...
//TODO - 1 - Try giving the right context
//TODO - 2 - Try updating the map when items are changed, not during close. The widget seems to be unmounted
//callback to trigger redraw if changed/dirty
//print("*** ShowBottomSheetClosed")
//mainAppState.
//context
//mainAppState.setState(() { })
//MainState.setState(() { reloadMapMarkers(ctx); });
settingsClosedCallBack();
//mainAppState.setState(() { settingsClosedCallBack(); })
}
);
}
}
Debug outs:
void _FAB_DrawMarkers() async {
BuildContext getContext = this.thisMapPageContext;
print("############ PROBLEM HERE:");
print(getContext);
print(this.widget);
print(this);
print(this.mounted);
print("############");
_FAB_DrawMarkers() - First run through, called after onMapCreated():
I/flutter (11115): ############ CONTEXT:
I/flutter (11115): SectionMain(state: _SectionMain#d8dda)
I/flutter (11115): SectionMain
I/flutter (11115): _SectionMain#d8dda
I/flutter (11115): true
I/flutter (11115): ############
_FAB_DrawMarkers() - Second run through, after trying a callback from closing BottomSheet:
I/flutter (11115): ############ CONTEXT:
I/flutter (11115): null
I/flutter (11115): null
I/flutter (11115): _SectionMain#2e084(lifecycle state: created, no widget, not mounted)
I/flutter (11115): false
I/flutter (11115): ############
The main error I get is:
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The getter 'owner' was called on null.
E/flutter (11115): Receiver: null
E/flutter (11115): Tried calling: owner
E/flutter (11115): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
E/flutter (11115): #1 Provider.of (package:provider/src/provider.dart:193:15)
E/flutter (11115): #2 _SectionMain._FAB_DrawMarkers (package:flutter_app/section_main.dart:255:38)
E/flutter (11115): #3 _SectionMain.callbackTest (package:flutter_app/section_main.dart:60:5)
E/flutter (11115): #4 settingsClosedCallBack (package:flutter_app/section_main.dart:344:18)
E/flutter (11115): #5 SectionSettings.onSheetShowContents.<anonymous closure>
where the error is of course pointing to the Provider line:
final selectedValue = Provider.of<MyAppStateContainer>(getContext, listen:false).getSomeValue; I was hoping that the whenCompleted() or then() from the bottomsheet would ensure that the main widget had been mounted before calling these functions. Perhaps that's the problem - that I'm calling them when the sheet has closed but before the main widget has a chance to remount.
I'm fairly new to Flutter, so many thanks for advice you can offer here. I'm sure it's something to do with me incorrectly passing context around, or not developing in the correct declarative way in Flutter.
Many thanks,
J

Related

Flutter : Unable to fetch json file data from an api

Hello Everyone I am new to flutter during practicing While trying to fetch JSON data from an api I am unable to complete the operation and receiving the below mentioned error. I have attached my entire program and error notification for your suggestions.
What this program is about?
I am trying to fetch the cryptocurrency price details from an api and trying to display the few details of that website in my app. while doing that the data type which i mentioned in the code creating some error and i tried to change the data type and other things but still it is not solved.
main.dart
import 'package:flutter/material.dart';
import 'package:fluttercrypto/home_page.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
void main() async {
List currencies = await getCurrency();
print(currencies);
runApp(MyApp(currencies));
}
class MyApp extends StatelessWidget {
final List _currencies;
MyApp(this._currencies); // This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.red,
),
home: HomePage(_currencies),
);
}
}
Future<List> getCurrency() async {
String cryptoUrl =
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids";
http.Response response = await http.get(Uri.parse(cryptoUrl));
return jsonDecode(response.body);
}
'''
**homepage.dart**
'''import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
final List currencies;
HomePage(this.currencies);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List currencies;
final List<MaterialColor> _colors = [Colors.blue, Colors.indigo, Colors.red];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Crypto Tracker"),
),
body: cryptoWidget(),
);
}
Widget cryptoWidget() {
return Container(
child: Column(
children: [
Flexible(
child: ListView.builder(
itemCount: widget.currencies.length,
itemBuilder: (BuildContext context, int index) {
final Map currency = widget.currencies[index];
final MaterialColor color = _colors[index % _colors.length];
return _getListItemUi(currency, color);
},
),
),
],
),
);
}
ListTile _getListItemUi(Map currency, MaterialColor color) {
return ListTile(
leading: CircleAvatar(
backgroundColor: color,
child: Text(currency['name'][0]),
),
title: Text(
currency['name'],
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: _getSubtitleText(
currency['current_price'], currency['price_change_24h']),
isThreeLine: true,
);
}
Widget _getSubtitleText(int priceUSD, String percentageChange) {
TextSpan priceTextWidget = new TextSpan(
text: "\$$priceUSD\n", style: TextStyle(color: Colors.black));
String percentageChangeText = "24 hour : $percentageChange%";
TextSpan percentageChangeTextWidget;
if (double.parse(percentageChange) > 0) {
percentageChangeTextWidget = TextSpan(
text: percentageChangeText,
style: TextStyle(color: Colors.green),
);
} else {
percentageChangeTextWidget = TextSpan(
text: percentageChangeText,
style: TextStyle(color: Colors.red),
);
}
return RichText(
text: TextSpan(children: [priceTextWidget, percentageChangeTextWidget]),
);
}
}'''
**ERROR**
'''
======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building:
type 'double' is not a subtype of type 'String'
When the exception was thrown, this was the stack:
#0 _HomePageState._getListItemUi (package:fluttercrypto/home_page.dart:54:46)
#1 _HomePageState.cryptoWidget.<anonymous closure> (package:fluttercrypto/home_page.dart:34:24)
#2 SliverChildBuilderDelegate.build (package:flutter/src/widgets/sliver.dart:455:22)
#3 SliverMultiBoxAdaptorElement._build (package:flutter/src/widgets/sliver.dart:1201:28)
#4 SliverMultiBoxAdaptorElement.createChild.<anonymous closure> (package:flutter/src/widgets/sliver.dart:1214:55)
...
====================================================================================================
======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building:
type 'double' is not a subtype of type 'String'
When the exception was thrown, this was the stack:
#0 _HomePageState._getListItemUi (package:fluttercrypto/home_page.dart:54:46)
#1 _HomePageState.cryptoWidget.<anonymous closure> (package:fluttercrypto/home_page.dart:34:24)
#2 SliverChildBuilderDelegate.build (package:flutter/src/widgets/sliver.dart:455:22)
#3 SliverMultiBoxAdaptorElement._build (package:flutter/src/widgets/sliver.dart:1201:28)
#4 SliverMultiBoxAdaptorElement.performRebuild.processElement `enter code here`(package:flutter/src/widgets/sliver.dart:1145:67)
====================================================================================================
'''
It's probably because currency['price_change_24h'] isn't a string. When you pass it into _getSubtitleText do currency['price_change_24h'].toString().

trying to make json request from worldtime api flutter

I am trying to make a JSON request from world time API by using future builder when I tried to get the data from my asset folder which contains JSON data it works properly but when I try to get the data from the internet it crashes
here as you can see
this the main class
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green,
body: FutureBuilder(
future:
get('http://api.worldweatheronline.com/premium/v1/weather.ashx?key=65dbd1979bd445e58aa171529203010&q=Europe/London&format=json&num_of_days=1'),
builder: (context, snapshot) {
var myData = json.decode(snapshot.data.toString());
String jsonsDataString = myData.body.toString(); // toString of Response's body is assigned to jsonDataString
jsonsDataString = jsonDecode(jsonsDataString);
if (myData == null){
return Center(
child: Text(
'Loading',
style: TextStyle(fontSize: 30, color: Colors.red),
),
);
}else{
return Center(
child: Text(
myData,
style: TextStyle(fontSize: 30, color: Colors.red),
),
);
}
}));
}
}
this the error when I try to run the app
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following FormatException was thrown building FutureBuilder<Response>(dirty, state: _FutureBuilderState<Response>#2a0b7):
Unexpected character (at character 1)
Instance of 'Response'
^
The relevant error-causing widget was:
FutureBuilder<Response> file:///F:/FlutterProjects/learn_json/lib/main.dart:54:15
When the exception was thrown, this was the stack:
#0 _ChunkedJsonParser.fail (dart:convert-patch/convert_patch.dart:1394:5)
#1 _ChunkedJsonParser.parseNumber (dart:convert-patch/convert_patch.dart:1261:9)
#2 _ChunkedJsonParser.parse (dart:convert-patch/convert_patch.dart:926:22)
#3 _parseJson (dart:convert-patch/convert_patch.dart:31:10)
#4 JsonDecoder.convert (dart:convert/json.dart:495:36)
...
your key is not working , check it using Postman , and you have to await for the response

Format Exception, FutureBuilder dirty? 'Unexpected end of input (at character 1)'?

First time posting, I'm very new to coding, and I am trying to learn about loading local json into flutter.
I followed this tutorial https://www.youtube.com/watch?v=bTwTKwK3hGc to the letter and triple checked and cannot find any differences, and no errors show in the editor, but when I try to run the code I get a "FormatException" error.
The code:
import 'package:flutter/material.dart';
import 'dart:convert';
void main() => runApp(new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.teal,
),
home: new HomePage(),
));
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
List data;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Load Json Practice"),
),
body: new Container(
child: new Center(
child: new FutureBuilder(
future: DefaultAssetBundle
.of(context)
.loadString('load_json/stuff.json'),
builder: (context, snapshot) {
//decode json:
var mydata = jsonDecode(snapshot.data.toString());
return new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Text("Name: " + mydata[index]['name']),
],
),
);
},
itemCount: mydata == null ? 0 : mydata.length,
);
},
),
),
),
);
}
}
My Json file:
[{
"id":1,
"name":"example"
}
]
My error:
════════ Exception caught by widgets library ═══════════════════════════════════
The following FormatException was thrown building FutureBuilder<String>(dirty, state: _FutureBuilderState<String>#07cf5):
Unexpected end of input (at character 1)
^
User-created ancestor of the error-causing widget was
Center
lib/main.dart:25
When the exception was thrown, this was the stack
#0 _ChunkedJsonParser.fail (dart:convert-patch/convert_patch.dart:1392:5)
#1 _ChunkedJsonParser.close (dart:convert-patch/convert_patch.dart:510:7)
#2 _parseJson (dart:convert-patch/convert_patch.dart:30:10)
#3 JsonDecoder.convert (dart:convert/json.dart:493:36)
#4 JsonCodec.decode (dart:convert/json.dart:151:41)
...
I am still very bad at understanding errors I get, I have googled everything I can think of to try and solve this on my own and have no idea how to troubleshoot this problem further. Any help or suggestions would be appreciated. Thank you for your time.
I have test your code and your code is correct
Step 1 : please check json file directory, you can see picture 1
Step 2 : check pubspec.yaml setting (picture 2)
In pubspec.yaml, space and indent are important
Step 3 : check you json data, does it contains abnormal character (picture 3)
assets:
- load_json/

Can't Load Current location in Flutter application

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(() {});
});
}

How to get a list of objects from a JSON request with self signed certifcate

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