I created an language learning app, where the user can choose from various topics inside a language (f.e shopping, smalltalk, alphabet, consonants and so on). When the user taps a topic it will load a json file. The app worked totally fine and is already on the app store (it's called Sankofa). Now I wanted to add a new language and noticed that my Futurebuilder doesn't work anymore, thus the app won't load the json file.
The error I got is:
The following _TypeError was thrown building FutureBuilder(dirty, state: _FutureBuilderState#a072a):
type 'Null' is not a subtype of type 'List'
Here's the code that loads the json file:
class Json extends StatelessWidget {
// accepting language as a parameter
String topicname;
late String assettoload;
Json(this.topicname);
setasset() {
if (topicname == "farben") {
assettoload = "assets/lerndateien/farben.json";
} else if (topicname == "Fragewörter") {
assettoload = "assets/lerndateien/fragewoerter.json";
} else if (topicname == "Begrüßungen") {
assettoload = "assets/lerndateien/saetze.json";
} else if (topicname == "Konsonanten") {
assettoload = "assets/lerndateien/konsonanten.json";
} else if (topicname == "Selbstlaute") {
assettoload = "assets/lerndateien/selbstlaute.json";
} else if (topicname == "Wochentage") {
assettoload = "assets/lerndateien/wochentage.json";
} else if (topicname == "Zeitangaben") {
assettoload = "assets/lerndateien/zeitangaben.json";
} else if (topicname == "Einkaufen") {
assettoload = "assets/lerndateien/einkaufen.json";
} else if (topicname == "Konversation") {
assettoload = "assets/lerndateien/konversation.json";
} else if (topicname == "Richtungen") {
assettoload = "assets/lerndateien/richtungsangaben.json";
} else if (topicname == "Über mich") {
assettoload = "assets/lerndateien/uebermich.json";
} else if (topicname == "Wegweisung") {
assettoload = "assets/lerndateien/wegweisungen.json";
} else if (topicname == "Lebensmittel") {
assettoload = "assets/lerndateien/lebensmittel.json";
} else if (topicname == "Tiere") {
assettoload = "assets/lerndateien/tiere.json";
}
}
#override
Widget build(BuildContext context) {
// function called before the build
// string assettoload is avialable to the DefaultAssetBuilder
setasset();
// return to the FutureBuilder to load and decode JSON
return FutureBuilder(
future:
DefaultAssetBundle.of(context).loadString(assettoload, cache: false),
builder: (context, snapshot) {
List mydata = json.decode(snapshot.data.toString());
if (mydata == null) {
return Scaffold(
body: Row(
children: [
BackButton(),
Center(
child: Text(
"Lädt...",
),
),
],
),
);
} else {
return quizpage(mydata: mydata);
}
},
);
}
}
I tried a lot of things (f.e if mydata == true) but nothing worked. I bet it's just a small fix and I hope that someone can help me.
Thanks!
That is because you're trying to access the snapshots data when it hasn't finished loading yet (no matter if it will be null or not) by using toString().
Inside your Builder you need to handle the case while it is loading, before you can check if it loaded null.
builder: (context, snapshot) {
// This will execute when it is finished loading AND is not null
if(snapshot.hasData){
List mydata = json.decode(snapshot.data.toString());
if (mydata == null) {
return Scaffold(
body: Row(
children: [
BackButton(),
Center(
child: Text(
"Lädt...",
),
),
],
),
);
} else {
return quizpage(mydata: mydata);
}
}
/* other handling, example */
return CircularProgressIndicator();
},
...
Now, as you'll see in your IDE, the snapshot can have multiple ConnectionStates. See here for a decent example of how to handle other states.
Related
My question is to help me figure out the logic of piecing these parts together.
I have
1 - An API returning JSON which contains a List<>? with data in it replicated ~5 times at the endpoint headsign
2 - A Metadata pop up which is populated by a _showDialog function with data from above
API
3 - A Listbody function (within _showDialog) to show the data in Text
My API is called
Future<di.Departures?> getDepartures(id) async {
var client = http.Client();
di.Departures? departures;
try{
var response = await client.get(Uri.parse('https_call'));
if (response.statusCode == 200) {
var jsonString = response.body;
var jsonMap = json.decode(jsonString);
departures = di.Departures.fromJson(jsonMap);
}
} catch(e) {
print("Exception Happened: ${e.toString()}");
}
}
How the data is returned
"boards": [
{
"place": {
x
x
},
x
},
"departures": [
{
"time": "2022-02-21T10:53:00Z",
"transport": {
"name": "District",
"color": "#007A33",
"textColor": "#FFFFFF",
>>> "headsign": "Ealing Broadway Station",
"shortName": "District",
"longName": "Ealing
},
"agency": {
x
}
},
The API data is added to the individual mapMarker and passed through to the metadata pop up _showDialog via _hereMapController
...
getDepartures(id).then((departures) async {
var boards = await getDepartures(id);
for (di.Board boards in boards!.boards!) {
var title = boards.place!.name.toString();
var headsign = boards.departures!.first.transport!.headsign;
var title = "$title";
Metadata metadata = new Metadata();
metadata.setString("key_poi", headsign);
metadata.setString("title", title.toString());
mapMarker.metadata = metadata;
_hereMapController.mapScene.addMapMarker(mapMarker);
_subwaymapMarkerList.add(mapMarker);
...
hereMapController then passes it to _showDialog
MapMarker topmostMapMarker = mapMarkerList.first;
Metadata? metadata = topmostMapMarker.metadata;
if (metadata != null) {
String headsign = metadata.getString("key_poi") ?? "No message found.";
String title = metadata.getString("title") ?? "No";
_showDialog(title, headsign);
return;
}
_showDialog("x", "No metadata attached.");
});
}
My Listbody in _showDialog
Future<void> _showDialog(String title, String headsign) async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Color(0xff2AC6FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
title: Text(title),
content: SingleChildScrollView(
scrollDirection: Axis.vertical,
child:
ListBody(
children: <Widget>[
Text(headsign,
style: TextStyle(
fontSize: 18,
color: Color(0xff2F2F2F),
fontWeight: FontWeight.w400,
),
),
],
),
...
As of now, my pop-up shows the text value of the first example returned by the API.
I want to take the values returned (up to 5 max for example) and display them in a list on the pop-up.
I presume I will need to form a ListView somewhere along?
The problem I am having is needing to use the constructor first when selecting the data I cannot see another way to take all examples returned (up to 5 max) of the specific field headsign.
Thank you
I'm getting this weird response while using http in Flutter/Dart. Similar code is working fine with other APIs endpoints but not this. Although the link has jSON data not any other format.
I have already check the following links and NONE of them is related to mine:
formatexception (formatexception: unexpected character (at character 1) json
Exception: FormatException: Unexpected character (at character 1)
Exception
Explanation
The exception I'm getting is similar. And I know that it is return HTML instead of jSON. But my link is NOT an HTML. Its a array of jSON Objects
Code
Apps Script Code for Google Sheets
function doGet(request) {
var sheet = SpreadsheetApp.openById("1CBPpqvdUpPaYMjxpX_9-ywMsErT06fD6AfzASWBFnnk");
var values = sheet.getActiveSheet().getDataRange().getValues();
var data = [];
for (var i = values.length - 1; i > 0; i--) {
var row = values[i];
var story = {};
story['Latitude'] = row[5];
story['Longitude'] = row[6];
console.log(story['Longitude'], story['Longitude']);
data.push(story);
}
return ContentService
.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
}
Model Class
class StoryList {
final List<Story> stories;
StoryList({this.stories});
factory StoryList.fromJson(List<dynamic> parsedJson) {
List<Story> story = new List<Story>();
story = parsedJson.map((i) => Story.fromJSON(i)).toList();
return new StoryList(stories: story);
}
}
class Story {
final String longitude;
final String latitude;
Story({this.latitude, this.longitude});
factory Story.fromJSON(Map<String, dynamic> json) {
return Story(
longitude: json['Longitude'],
latitude: json['Latitude'],
);
}
}
Controller Class
You won't be able to access the link its restricted for organization use only
class StoryController {
Future<StoryList> getCountryData() async {
String url =
'https://script.google.com/a/macros/storius.app/s/AKfycbyzx4kIlVdTC9QVVBovVfWMDFWdk9noomDJV4XcyDApnsMYTe68u0mL/exec';
final response = await http.get(url);
if (response.statusCode == 200) {
final jsonRes = json.decode(response.body);
return StoryList.fromJson(jsonRes);
} else {
throw Exception("Failed due to Network Error");
}
}
}
View Class
For the time being I'm only getting Longitude for testing purposes.
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: StoryController().getCountryData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data.stories[index].longitude),
);
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
));
}
}
As per our Discord discussion, when you printed response.body at my suggestion you see that you are getting an html response from the url, not json as you expected.
The problem is with authentication on the linked website rather than with the Flutter/Dart code. You need to resolve that authentication issue.
It is recommended that when testing new code that accesses a database or a url to always print out, or use a debugger to view, the returned data. Never assume you are getting what you expect.
Issue in brief:
trying to access location data of the user in background using location and workManager plugin.
Currently with the code mentioned below i am able to access the location information if the application is open, Since callbackDispatcher is a top level function i am not able to call the location plugin.
location plugin works when a call is done inside of the class. I am trying a way to access _getlocation() from callbackDispatcher, I am getting PlatformException(NO_ACTIVITY).
Things I have tried:
found few other guys facing similar issue here, here and here
Tired all these steps and no luck.
import 'package:flutter/material.dart';
import 'package:location/location.dart';
import 'package:workmanager/workmanager.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
Location location = new Location();
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) {
if (task == "simplePeriodicTask") {
print("task working");
_getLocation();
}
return Future.value(true);
});
}
LocationData _location;
String _error;
double lat;
double long;
_getLocation() async {
_error = null;
try {
var _locationResult = await location.getLocation();
_location = _locationResult;
lat = _location.latitude;
long = _location.longitude;
} on PlatformException catch (err) {
_error = err.code;
}
if (_error == null) {
// _check();
print(lat);
} else {
//dialog
print(_error);
}
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Workmanager.initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode:
true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
_checkPermissions();
}
// Permission for location
PermissionStatus _permissionGranted;
// final Location location = new Location();
_checkPermissions() async {
PermissionStatus permissionGrantedResult = await location.hasPermission();
setState(() {
_permissionGranted = permissionGrantedResult;
});
if (_permissionGranted == PermissionStatus.DENIED) {
_requestPermission();
} else if (_permissionGranted == PermissionStatus.GRANTED) {
_checkService();
}
}
_requestPermission() async {
if (_permissionGranted != PermissionStatus.GRANTED) {
PermissionStatus permissionRequestedResult =
await location.requestPermission();
setState(() {
_permissionGranted = permissionRequestedResult;
});
if (permissionRequestedResult != PermissionStatus.GRANTED) {
return;
} else if (permissionRequestedResult == PermissionStatus.GRANTED) {
_checkService();
}
}
}
//Permission ends
//services enabled function
bool _serviceEnabled;
_checkService() async {
bool serviceEnabledResult = await location.serviceEnabled();
setState(() {
_serviceEnabled = serviceEnabledResult;
});
if (_serviceEnabled == false) {
_requestService();
} else {
// _getLocation();
}
}
_requestService() async {
if (_serviceEnabled == null || !_serviceEnabled) {
bool serviceRequestedResult = await location.requestService();
setState(() {
_serviceEnabled = serviceRequestedResult;
});
if (!serviceRequestedResult) {
return;
} else {
// _getLocation();
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dart'),
),
body: Column(children: <Widget>[
RaisedButton(
child: Text('get Location'),
onPressed: () {
Workmanager.registerPeriodicTask(
"2",
"simplePeriodicTask",
// When no frequency is provided the default 15 minutes is set.
// Minimum frequency is 15 min. Android will automatically change your frequency to 15 min if you have configured a lower frequency.
);
print('task registered');
_getLocation();
}),
RaisedButton(
onPressed: () async {
await Workmanager.cancelAll();
print('task Destroyd');
},
child: Text("cancel"),
),
]),
);
}
}
Trying to access _getlocation() from callbackDispatcher();
Any help on this is greatly appreciated.
I was facing same issue recently. location package not work with WorkManager plugin, I dont know the reason but here is my solution;
/// This Function calls only from WorkManager
/// Used GeoLocator instead of Location package due to PlatformException(NO_ACTIVITY) error throwing
Future<String> getPlaceMarkLocationWhileAppOff() async {
Geolocator geoLocator = Geolocator()..forceAndroidLocationManager = true;
var _position = await geoLocator.getCurrentPosition(
// desiredAccuracy: LocationAccuracy.high,
);
var value = await geoLocator.placemarkFromCoordinates(_position.latitude, _position.longitude);
return _placeMark = "${value.first.subLocality}\n${value.first.subAdministrativeArea}";
}
Used Geolocator package when app offline and used Location package when app online..
I hope it will help..
I'm new to flutter and after some good progress got stuck on what i though should be a simple loop. I'm able to show a single result from the json, but unable to display them all.
for(var i = 0; i < snapshot.data.posts.length; i++){
return Text(snapshot.data.posts[i].name + " - " + snapshot.data.posts[i].id + " - " + i.toString());
}
As this coded returns and outputs the first result of the dataset.
Mr. Nice - 1 - 0
The problems is that i can can't get it to output the rest of the results in the dataset. The external JSON which i'm parsing reads as follows (in full)
[{"id":1,"name":"Mr. Nice"},{"id":2,"name":"Narco"},{"id":3,"name":"Bombasto"},{"id":4,"name":"Celeritas"},{"id":5,"name":"Magneta"},{"id":6,"name":"Super Adueduct"},{"id":7,"name":"Mr. Test"}]
This full flutter page (which has been separated into it's own route) is as follows:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<PostsList> fetchPostsList() async {
final response =
await http.get('http://localhost:8888/heroes');
if (response.statusCode == 200) {
return PostsList.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
class PostsList {
final List<Post> posts;
PostsList({
this.posts,
});
factory PostsList.fromJson(List<dynamic> parsedJson) {
List<Post> posts = new List<Post>();
posts = parsedJson.map((i)=>Post.fromJson(i)).toList();
return new PostsList(
posts: posts
);
}
}
class Post{
final String id;
final String name;
Post({
this.id,
this.name
});
factory Post.fromJson(Map<String, dynamic> json){
return new Post(
id: json['id'].toString(),
name: json['name'],
);
}
}
class HeroesPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
home: Scaffold(
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<PostsList>(
future: fetchPostsList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
for(var i = 0; i < snapshot.data.posts.length; i++){
return Text(snapshot.data.posts[i].name + " - " +
snapshot.data.posts[i].id + " - " + i.toString());
}
//return Text(snapshot.data.posts[1].name);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner
return CircularProgressIndicator();
},
),
),
),
);
}
Now i may be going about displaying simple api data all wrong, it's just hard to find a non-firebase tutorial working with flutter and api's.
FYI - The API running on localhost was set-up with Aqueduct by following this tutorial: https://aqueduct.io/docs/tut/getting-started/
The code and classed where based on the Flutter docs tutorial (yet slightly modified for my json structure): https://flutter.io/cookbook/networking/fetch-data/
You're really close! The problem is that return in your for loop inside the FutureBuilder builder function. The return breaks out of the loop after the first iteration, so you'll only ever get the first element of your data.
Instead, you need to generate a list of all widgets in the data and then return the list.
Two possible ways I can think to solve this (depends on your desired display widget):
=> Use a ListView.builder to handle the iteration for you:
if (snapshot.hasData) {
List<Post> yourPosts = snapshot.data.posts;
return ListView.builder(
itemCount: yourPosts.length,
itemBuilder: (BuildContext context, int index) {
// Whatever sort of things you want to build
// with your Post object at yourPosts[index]:
return Text(yourPosts[index].toString());
}
);
}
=> Build a List<Widget> and return a column (closest to your original approach):
if (snapshot.hasData) {
List <Widget> myPosts;
for(var i = 0; i < snapshot.data.posts.length; i++){
myPosts.add(Text(snapshot.data.posts[i].name + " - " +
snapshot.data.posts[i].id + " - " + i.toString())
);
}
return Column( children: myPosts );
}
i'm trying to do a Dashboard with multiples jsons requests, but i want that request work one per one, like when finish first request start the second, when finish second start the third when finish third start the N.
my list code:
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(),
new RelatorioMensals(),
new RelatorioDiasDaSemanas(),
new RelatorioVendasTotalidasPorPeriodo(),
new RelatorioDasVendasTotsProdutos(),
]
)
)
]
),
this new classes calls, returns for me request. Anyone knows how to delay it?
First, the parent widget should return a progress bar when the necessary data is not available.
A service will be called an initState to fetch data from the backend. when data is ready setState() will be called to redraw the widget.
Look at this example:
class _TestWidgetState extends State<TestWidget> {
var data;
#override
void initState() {
data = NetworkService.getData().then((data) {
setState(() {
this.data = data;
});
});
}
#override
Widget build(BuildContext context) {
if (data == null) {
return CircularProgressIndicator();
} else {
return
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(data: data),
new RelatorioMensals(data: data),
new RelatorioDiasDaSemanas(data: data),
new RelatorioVendasTotalidasPorPeriodo(data: data),
new RelatorioDasVendasTotsProdutos(data: data),
]
)
)
]
);
}
}
}
class NetworkService {
final JsonDecoder _decoder = new JsonDecoder();
static String data1;
static String data2;
static getData() async {
if (data1 == null || data2 == null) {
await fetchFromServer();
}
return {'data1': data1, 'data2': data2};
}
static fetchFromServer() async {
data1 = (await http.get('url')).body;
data2 = (await http.get('url')).body;
}
}
Future.delayed(const Duration(milliseconds: 500), () {
print(" This line is executed after 5 seconds");
});