I'm designing an app with a List-List-Detail view (an overview of lists, each list, details of the selected item from each list) that pulls all of its data from static JSON files.
I'm wondering what's the best structure for reading, parsing, and displaying the JSON files. Should I design models for each view: the overview of all lists, each list, and each item and have those as separate files? Should all of the asset loading and JSON parsing take place in those model files? Should the each view only accept parsed data in the form of a PODO?
I've been following the instructions here for the overall design, but I'm stuck on the best way to work with the JSON files, since they add an async element to Widget design.
Yes you need to create the Model class to handle the JSON response,
Either you can use the same model class to list-list, but dont forget to add child list items to same model
here is the sample example
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response =
await client.get('https://jsonplaceholder.typicode.com/photos');
// Use the compute function to run parsePhotos in a separate isolate
return compute(parsePhotos, response.body);
}
// A function that will convert a response body into a List<Photo>
List<Photo> parsePhotos(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => new Photo.fromJson(json)).toList();
}
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
Photo({this.albumId, this.id, this.title, this.url, this.thumbnailUrl});
factory Photo.fromJson(Map<String, dynamic> json) {
return new Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Isolate Demo';
return new MaterialApp(
title: appTitle,
home: new MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new FutureBuilder<List<Photo>>(
future: fetchPhotos(new http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? new PhotosList(photos: snapshot.data)
: new Center(child: new CircularProgressIndicator());
},
),
);
}
}
class PhotosList extends StatelessWidget {
final List<Photo> photos;
PhotosList({Key key, this.photos}) : super(key: key);
#override
Widget build(BuildContext context) {
return new GridView.builder(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
return new Image.network(photos[index].thumbnailUrl);
},
);
}
}
Related
I wrote this code recently but it doesn't get me the result!
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'package:pics/src/widgets/image_list.dart';
import 'models/image_model.dart';
import 'dart:convert';
class App extends StatefulWidget {
createState() {
return AppState();
}
}
class AppState extends State<App> {
int counter = 0;
List<ImageModel> images = [];
void fetchImage() async {
counter++;
final Uri rep =
Uri.parse('https://jsonplaceholder.typicode.com/photos/$counter');
var response = await get(rep);
var imageModel = ImageModel.fromJson(json.decode(response.body));
setState(() {
images.add(imageModel);
});
}
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: ImageList(images),
appBar: AppBar(
title: const Text('Lets see some images'),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: fetchImage,
),
),
);
}
}
The terminal say this:
The following NetworkImageLoadException was thrown resolving an image
codec: HTTP request failed, statusCode: 403,
https://via.placeholder.com/600/92c952
When the exception was thrown, this was the stack:
#0 NetworkImage._loadAsync (package:flutter/src/painting/_network_image_io.dart:117:9)
Image provider: NetworkImage("https://via.placeholder.com/600/92c952",
scale: 1.0) Image key:
NetworkImage("https://via.placeholder.com/600/92c952", scale: 1.0)
and this is image list class if you need to read it:
import 'package:flutter/material.dart';
import '../models/image_model.dart';
class ImageList extends StatelessWidget {
final List<ImageModel> images;
ImageList(this.images);
Widget build(context) {
return ListView.builder(
itemCount: images.length,
itemBuilder: (context, int index) {
return Image.network(images[index].url);
},
);
}
}
and this is Image Model:
class ImageModel {
late int id;
late String title;
late String url;
ImageModel(this.id, this.title, this.url);
ImageModel.fromJson(Map<String, dynamic> parsedJson) {
id = parsedJson['id'];
title = parsedJson['title'];
url = parsedJson['url'];
}
}
You can use web render, run with --web-renderer html
flutter run -d c --web-renderer html
-d to select device, where c is chrome
I am trying to retrieve data from a Realtime database in Firebase to Flutter. The data should be parsed to be used in the building of a listview inside a future builder. However, after I execute the code I got an error that displayed on the Emulator screen. My understanding is that there is a type mismatch inside the code of firebaseCalls method. Below is my code Main.dart, data model, Firebase data, and Error Message. Any help to figure out the issue is appreciated. Thanks in advance!
Main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'datamodel.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final ref = FirebaseDatabase.instance.ref();
Future<List<Menu>> firebaseCalls(DatabaseReference ref) async {
DataSnapshot dataSnapshot = await ref.child('Task').get();
String? jsondata =dataSnapshot.value as String?; // just in case String is not working
//String jsondata = dataSnapshot.children;// value;//[0]['Task'];// should be dataSnapshot.value
// Decode Json as a list
final list = json.decode(jsondata!);// as List<dynamic>;
return list.map((e) => Menu.fromJson(e)).toList();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
//drawer: _drawer(data),
appBar: AppBar(
title: const Text('الصف السادس العلمي'),
),
body: FutureBuilder(
future: firebaseCalls(ref), // async work
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Press button to start');
case ConnectionState.waiting:
return new Text('Loading....');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
_buildTiles(snapshot.data[index]),
);
}
} // builder
)
)
);
}
////////////////////////////////////////////
Widget _buildTiles(Menu list) {
if (list.subMenu?.length == 0)
return new ListTile(
leading: Icon(list.icon),
title: Text(
list.name!,
style: TextStyle(
fontSize: list.font?.toDouble(), fontWeight: FontWeight.bold),
),
onTap: () => debugPrint("I was clicked"),
);
return new ExpansionTile(
leading: Icon(list.icon),
title: Text(
list.name!,
style: TextStyle(
fontSize: list.font?.toDouble(), fontWeight: FontWeight.bold),
),
children: list.subMenu!.map(_buildTiles).toList(),
);
}//_buildTiles
}
datamodel.dart
import 'package:flutter/material.dart';
class Menu {
String? name; // I added ?
IconData? icon;// I added ?
int? font;// I added ?
List<Menu>? subMenu= [];// I added ?
Menu({this.name, this.subMenu, this.icon,this.font});
Menu.fromJson(Map<String, dynamic> json) {
name = json['name'];
font = json['font'];
icon = json['icon'];
if (json['subMenu'] != null) {
//subMenu?.clear(); // I added ? it also recomand using !
json['subMenu'].forEach((v) {
//subMenu?.add(new Menu.fromJson(v));
subMenu?.add(Menu.fromJson(v));
});
}
}
}
Database:
Error message:
The problem is here:
DataSnapshot dataSnapshot = await ref.child('Task').get();
String? jsondata =dataSnapshot.value as String?;
If we look at the screenshot of the database you shared, it's clear that the value under the /Task path is not a string. It is in fact an entire object structure, which means you get back a Map<String, Object> from dataSnapshot.value. And since that's not a String, you get the error that you get.
The proper way to get the value of the entire Task node is with something like:
Map values = dataSnapshot.value;
And then you can get for example the name with:
print(values["name"]);
Alternatively you get get the child snapshot, and only then get the string value from it, with something like:
String? name = dataSnapshot.child("name")?.value as String?;
so It's my first time using API on flutter I just take the tutorial copy paste and change some line for the test I just want to learn more about API's so don't blame me if I made a dumb mistake or something like .
I try to get a title from the APi and get this error if somoene can help that will be really great.
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://api.mangadex.org/manga'));
if (response.statusCode == 200) {
return Album.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load album');
}
}
class Album {
final int year;
final String title;
const Album({
required this.year,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
year: json['year'],
title: json['title'],
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
actually what goes wrong here is that json response you are getting back from the API doesn't contain a year or title fields. So, both json['year'] and json['title'] will be null. And since you are assigning them to Album.year and Album.title which are not nullable, the whole app breaks.
I tried calling the API URI, and found that the JSON structure is a bit different.
You might wan this code for your fromJson method for it to work.
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
year: json['data'][0]['attributes']['year'],
title: json['data'][0]['attributes']['title']["en"],
);
}
Please note that json['data'] is actually an array of albums, in this code we're just fetching ONLY the first element inside that array.
I am going to create a JSON data in my flutter application and allow users to choice what item that theirs favorite to. This is the class from Doa, and the data i take it from local JSON file.
import 'dart:convert';
List<Doa> doaFromJson(String str) =>
List<Doa>.from(json.decode(str).map((x) => Doa.fromJson(x)));
String doaToJson(List<Doa> data) =>
json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Doa {
Doa({
this.id,
this.grup,
this.judul,
this.lafaz,
this.latin,
this.arti,
this.tentang,
this.mood,
this.tag,
this.fav,
});
final int id;
final String grup;
final String judul;
final String lafaz;
final String latin;
final String arti;
final String tentang;
final String mood;
final String tag;
bool fav;
factory Doa.fromJson(Map<String, dynamic> json) => Doa(
id: json["id"],
grup: json["grup"],
judul: json["judul"],
lafaz: json["lafaz"],
latin: json["latin"],
arti: json["arti"],
tentang: json["tentang"],
mood: json["mood"],
tag: json["tag"],
fav: json["fav"],
);
Map<String, dynamic> toJson() => {
"id": id,
"grup": grup,
"judul": judul,
"lafaz": lafaz,
"latin": latin,
"arti": arti,
"tentang": tentang,
"mood": mood,
"tag": tag,
"fav": fav,
};
}
And this is my main page that show the list of the JSON data.
import 'package:flutter/material.dart';
import 'package:json_test/class/doa.dart';
import 'package:json_test/page/DoaPage.dart';
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
List<Doa> doaList;
bool _isInit = true;
Future<void> fetchDoa(BuildContext context) async {
final jsonstring =
await DefaultAssetBundle.of(context).loadString('assets/doa.json');
doaList = doaFromJson(jsonstring);
_isInit = false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("JSON Data test"),
),
body: Container(
child: FutureBuilder(
future: _isInit ? fetchDoa(context) : Future(null),
builder: (context, _) {
if (doaList.isNotEmpty) {
return ListView.builder(
itemCount: doaList.length,
itemBuilder: (BuildContext context, int index) {
Doa doa = doaList[index];
return Card(
margin: EdgeInsets.all(8),
child: ListTile(
title: Text(doa.judul),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
DoaPage(
doa: doa,
)));
},
trailing: IconButton(
icon: Icon(
doa.fav
? Icons.favorite
: Icons.favorite_border,
color: doa.fav ? Colors.red : null,
),
onPressed: () =>
setState(() => doa.fav = !doa.fav),
)));
},
);
}
return CircularProgressIndicator();
})));
}
}
The favorite button is worked. But, when I close the application, all of favorited items will be lost.
The result from my code shown here
After I give some 'love' for the items, when I close the app and re-open it, all of favorited items will lost. Anyone can give me some advice for my code? Thank you very much.
You should save the favorite item local phone or you can use apı service. you don't save the that item and when you close the application that item is a coming null
You can use this package for the save favorite item
shared_preferences
or
hive
I have used the flutter cookbook to create a Future in order to convert a single Map into a card. However I want to do this with a list of Maps. I know I need to create a list and then use the futurebuilder to return a list view, but I am unsure how to add to the apitest() method to add each Map to a list to then convert. Any help is much appreciated.
This is main.dart:
import 'package:flutter/material.dart';
import './viewviews.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'API TEST',
theme: new ThemeData(
primarySwatch: Colors.purple,
backgroundColor: Colors.black,
),
home: CardPage(),
);
}
}
This is my class to construct the object:
import 'package:flutter/material.dart';
class Post {
final String appName;
final String brandName;
final int views;
Post(
{#required this.appName, #required this.brandName, #required this.views});
factory Post.fromJson(Map<String, dynamic> json) {
return (Post(
views: json['Views'],
brandName: json['brandname'],
appName: json['appname']));
}
}
and finally the code I am using to make the api call and use the futurebuilder:
import 'dart:async';
//final List<Post> cardslist = [];
class CardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(title: Text('API TEST')),
body: ListView.builder(
itemBuilder: cardbuilder,
itemCount: 1,
));
}
}
Future<Post> testapi() async {
final apiresponse =
await http.get('https://myriadapp-55adf.firebaseio.com/Views.json');
print(apiresponse.body);
return Post.fromJson(json.decode(apiresponse.body));
}
Widget cardbuilder(BuildContext context, int index) {
return Container(
margin: EdgeInsets.all(20.0),
child: FutureBuilder<Post>(
future: testapi(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.appName);
} else if (snapshot.hasError) {
return Text(snapshot.hasError.toString());
}
return Center(child: CircularProgressIndicator());
}));
}
I have commented out the code to create the list but I know I will need this, I just need to decode the json to add a list of Maps to it and then use Futurebuilder to return the listview. Thank you.
Your json doesn't actually return a json array, it actually returns a map with arbitrary key names like View1, so we'll iterate that map. You need to hoist your FutureBuilder up to the point where you can deal with the whole json at once, so at the page level. So CardPage becomes
class CardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('API TEST')),
body: FutureBuilder<Map>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final Map<String, dynamic> data = snapshot.data;
final List<Container> cards = data.keys
.map((String s) => makeCard(Post.fromJson(data[s])))
.toList();
return ListView(
children: cards,
);
} else if (snapshot.hasError) {
return Text(snapshot.hasError.toString());
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
);
}
Container makeCard(Post post) {
return Container(
margin: EdgeInsets.all(20.0),
child: Text(post.appName),
);
}
Future<Map> fetchData() async {
final apiresponse =
await http.get('https://myriadapp-55adf.firebaseio.com/Views.json');
print(apiresponse.body);
return json.decode(apiresponse.body);
}
}