How to make two dependent async requests in flutter? - android

The question seems to be a little dumb, but as someone who never worked with async functions before it is not so trivial.
I fetch some json data from http request and build a list. For example, lets say userid and username.
[
{"userid":1,"username":"JohnDoe"},
{"userid":2,"username":"SamSmith"}
]
Code:
Future<UsersList> fetchUsers() async {
final response = await http.get(
Uri.encodeFull('https://www.myurl.com/users'),
headers: {'Accept': 'application/json'});
if (response.statusCode == 200) {
return UsersList.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load users');
}
}
class User {
final String userid;
final String username;
String tag;
User({this.userid, this.username});
factory User.fromJson(Map<String, dynamic> json){
return User(
userid: json['userid'],
username: json['username'],
);
}
}
class UsersList {
final List<User> Users;
UsersList({this.Users});
factory UsersList.fromJson(List<dynamic> parsedJson) {
List<User> Users = new List<User>();
Users = parsedJson.map((i) => User.fromJson(i)).toList();
return new UsersList(Users: Users);
}
}
class UsersTab extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return UsersTabState();
}
}
class UsersTabState extends State<UsersTab> {
Future<UsersList> Users;
#override
void initState() {
super.initState();
Users = fetchUsers();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('users'), backgroundColor: Colors.blue),
body: Center(
child: FutureBuilder<usersList>(
future: users,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.users.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Text('User: ' +
snapshot.data.users[index].username +
'\nTag: ' +
snapshot.data.users[index].tag),
);
},
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
),
),
);
}
}
Now, I also have local data from shared_preferences where I can tag users by id. So I have a function like
Future<String> getTag(String id) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(id) ?? "none";
}
My question is, where can I call this function? It obviously must be before the FutureBuilder builds the list but after http request has finished. I had some ideas like initState of UsersTabState or User class constructor but it always ends up in a future somewhere where I would need a String.
What is the best way to get the locally stored tag into the User class?

So my solution is to put the getTag method inside the User class and make both User.fromJson and UsersList.fromJson into static methods which returns Future<User> and Future<UsersList>. By doing that, we can put all the awaiting into fetchUsers so this method will end up returning a UsersList object which are done after awaiting it.
Future<UsersList> fetchUsers() async {
final response = await http.get(
Uri.encodeFull('https://www.myurl.com/users'),
headers: {'Accept': 'application/json'});
if (response.statusCode == 200) {
return await UsersList.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load users');
}
}
class User {
final String userid;
final String username;
final String tag;
User({this.userid, this.username, this.tag});
static Future<User> fromJson(Map<String, dynamic> json) async {
final userId = json['userid'];
final tag = await _getTag(userId);
return User(
userid: json['userid'],
username: json['username'],
tag: tag
);
}
static Future<String> _getTag(String id) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(id) ?? "none";
}
}
class UsersList {
final List<User> Users;
UsersList({this.Users});
static fromJson(List<dynamic> parsedJson) async {
List<User> Users = new List<User>();
Users = await Future.wait(parsedJson.map((i) => User.fromJson(i));
return new UsersList(Users: Users);
}
}
class UsersTab extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return UsersTabState();
}
}
class UsersTabState extends State<UsersTab> {
Future<UsersList> Users;
#override
void initState() {
super.initState();
Users = fetchUsers();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('users'), backgroundColor: Colors.blue),
body: Center(
child: FutureBuilder<usersList>(
future: users,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.users.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Text('User: ' +
snapshot.data.users[index].username +
'\nTag: ' +
snapshot.data.users[index].tag),
);
},
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
),
),
);
}
}
One trick I have used is this to await on multiple Future's which will return a List:
Users = await Future.wait(parsedJson.map((i) => User.fromJson(i));
You can read about it here: https://api.dart.dev/stable/2.7.2/dart-async/Future/wait.html

Related

Stream not returning any value

I have a stream that apparently does not return a value. Instead of returning anything, the snapshot I use in my Streambuilder returns the yellow container (see code below) which is returned when my snapshot has no data. Any idea what causes this issue?
Below you will all functions, the stream as well as my Streambuilder.
Here is the updated stream. The otherUserId print statement is NOT printed. Maybe the error lies somewhere here.
Stream<List>? roomsListStream() async* {
try {
print("userId: $userId");
var rooms = FirebaseFirestore.instance
.collection("rooms")
.where("users", arrayContains: userId)
.orderBy("latestMessageTime", descending: true)
.snapshots();
rooms.map((QuerySnapshot query) {
List<RoomsListModel> retVal = [];
for (var element in query.docs) {
// get other user id
String otherUserId = element["users"][0] == userId
? element["users"][1]
: element["users"][0];
print("otherUserId: $otherUserId");
// get other user details
getOtherUser(otherUserId).then((value) {
retVal.add(RoomsListModel(
roomId: element.id,
otherUserId: otherUserId,
avatar: value["photoUrl"],
name: value["name"],
lastMessage: element["latestMessage"],
lastMessageTime: element["latestMessageTime"]));
});
}
print(retVal);
return retVal;
});
} catch (e) {
print("Error: $e");
}
}
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:blabber_tech/services/auth.dart';
import 'package:blabber_tech/services/chat_services.dart';
import 'package:rxdart/rxdart.dart';
import 'package:blabber_tech/models/room_model.dart';
// Rooms List Model
class RoomsListModel {
String roomId;
String otherUserId;
String avatar;
String name;
String lastMessage;
Timestamp lastMessageTime;
RoomsListModel(
{required this.roomId,
required this.otherUserId,
required this.avatar,
required this.name,
required this.lastMessage,
required this.lastMessageTime});
}
class MyChatsScreen3 extends StatefulWidget {
static const String id = "mychats3_screen";
#override
State<MyChatsScreen3> createState() => _MyChatsScreenState();
}
// get other user details
Future getOtherUser(String id) async {
// get other user profile
var user = await FirebaseFirestore.instance
.collection("users")
.doc(id)
.get()
.then((value) => value.data()) as Map<String, dynamic>;
// return other user profile
return user;
}
class _MyChatsScreenState extends State<MyChatsScreen3> {
// get current user id
String userId = AuthService().getUserId();
// get all active chats
**Stream<List>? roomsListStream() {**
try {
FirebaseFirestore.instance
.collection("rooms")
.where("users", arrayContains: userId)
.orderBy("latestMessageTime", descending: true)
.snapshots()
.map((QuerySnapshot query) {
List<RoomsListModel> retVal = [];
query.docs.forEach((element) {
retVal.add(RoomsListModel(
roomId: element.id,
otherUserId: element["users"][0] == userId
? element["users"][1]
: element["users"][0],
avatar: element["photoUrl"],
name: element["name"],
lastMessage: element["latestMessage"],
**lastMessageTime: element["latestMessageTime"]**));
});
return retVal;
});
} catch (e) {
print("Error: $e");
}
}
// List builder for mobile app
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
// create listview of all chats of current user and show last message and other user name and photo
child: **StreamBuilder(**
stream: roomsListStream(),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
**if (snapshot.hasData) {**
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
// show other user profile photo
//backgroundImage:
//NetworkImage(otherUser["profilePhotoUrl"]),
),
//title: Text(snapshot.data[index]["userName"]),
subtitle: Text(snapshot.data[index]["lastMessage"]),
);
},
);
} else {
return Container(
color: Colors.yellow,
);
}
},
),
),
);
}
}
You forget to await for FirebaseFirestore result:
Stream<List>? roomsListStream() async* {
try {
var rooms = await FirebaseFirestore.instance
.collection("rooms")
.where("users", arrayContains: userId)
.orderBy("latestMessageTime", descending: true)
.snapshots();
await rooms.map((QuerySnapshot query) async*{
List<RoomsListModel> retVal = [];
query.docs.forEach((element) {
retVal.add(RoomsListModel(
roomId: element.id,
otherUserId: element["users"][0] == userId
? element["users"][1]
: element["users"][0],
avatar: element["photoUrl"],
name: element["name"],
lastMessage: element["latestMessage"],
lastMessageTime: element["latestMessageTime"]));
});
yield retVal;
});
} catch (e) {
print("Error: $e");
}
}
and also change this:
if (snapshot.hasData) {
to this:
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {

infinite scrolling from flutter application that fetch data from wordpress rest api based on categories fetch data not according to their category

infinite scrolling from flutter application that fetches data from WordPress rest API based on categories... I tried to fetch data based on categories but it display all data on all categories tabs ... I just want to know how to filter news in their category it was working fine before I try to add infinite scrolling
Categories List are loading in all the tabs but the problem is when changing tabs, previous tab category list data is loading on the top of the next tab. How to overcome this problem. First tab data is loading when changing to the second tab on the top and In third tab First tab and Second tab data is loading on the top.
this is config.dart
class Config {
static String? apiURL = "https://hageez.tech/wp-json/wp/v2/";
static String? categoryURl = "categories?per_page=100";
static String? postURL = "latest-posts/?page_size=3&category_id=";
static String? postDetailURL = 'post-details/?id=';
static String? dateFormat = "dd-MM-yyyy";
}
this is API fetch for post and categories
class APIService {
static var client = http.Client();
static Future<List<CategoryModel>?> fetchCategories() async {
var response =
await client.get(Uri.parse(Config.apiURL! +
Config.categoryURl!));
if (response.statusCode == 200) {
var jsonString = response.body;
return categoryFromJson(jsonString);
} else {
return null;
}
}
static Future<List<NewsModel>?> fetchPosts(
int categoryId,
int pageNumber,
) async {
var url = Config.apiURL! +
Config.postURL! +
categoryId.toString() +
"&page_no=" +
pageNumber.toString();
var response = await client.get(Uri.parse(url));
if (response.statusCode == 200) {
var jsonString = response.body;
return postsFromJson(jsonString);
}
return null;
}
this is controller part using getx for category and post
class CategoriesController extends GetxController
with GetSingleTickerProviderStateMixin {
var isloading = true.obs;
var categoriesList = <CategoryModel>[].obs;
#override
void onInit() {
// TODO: implement onInit
fetchCategories();
super.onInit();
}
Future<void> fetchCategories() async {
try {
isloading(true);
var categories = await APIService.fetchCategories();
if (categories!.length > 0) {
categoriesList.clear();
categoriesList.addAll(categories);
}
} finally {
isloading(false);
}
}
}
this is postcontroller
class postcontroller extends GetxController {
var isloading = true.obs;
var postList = <NewsModel>[].obs;
#override
void onInit() {
// TODO: implement onInit
fetchPosts();
super.onInit();
}
Future<void> fetchPosts(
{int categoryId = 1, int pageNumber = 0, int totalrecords = 0}) async {
try {
if (postList.length == 0 || pageNumber == 0) {
isloading(true);
postList.clear();
}
if (postList.length < totalrecords) {
var posts = await APIService.fetchPosts( categoryId,pageNumber,);
if (posts != null) {
postList.addAll(posts);
print(posts);
}
}
} finally {
isloading(false);
}
}
}
the following is postpage that have tabs based on categories and for the post inject data from newspage.dart
class trytab extends StatefulWidget {
trytab({Key? key}) : super(key: key);
#override
State<trytab> createState() => _trytabState();
}
class _trytabState extends State<trytab> with SingleTickerProviderStateMixin {
final CategoriesController categoriesController =
Get.put(CategoriesController());
// List<Widget> tabs =[];
#override
void initState() {
// TODO: implement initState
super.initState();
// tabs.add(tab("tabName"));
// tabs.add(tab(" hey brother "));
// tabs.add(tab("3"));
// tabs.add(tab("5"));
// tabs.add(tab("tab6Name"));
}
#override
Widget build(BuildContext context) {
return Center(
child: Obx(() {
return DefaultTabController(
length: categoriesController.categoriesList.length,
child: Scaffold(
appBar: AppBar(
title: Text("API TEST"),
bottom: TabBar(
isScrollable: true,
tabs:categoriesController.categoriesList
.map((model) => tab(model.categoryName))
.toList()),
),
body: TabBarView(
children: categoriesController.categoriesList.map((model) {
return newspage(
categoryID: model.categoryId,
isRelod: true,
totalRecords: model.count,
);
}).toList(),
),
),
);
}),
);
}
}
newspge.dart
class newspage extends StatefulWidget {
late int? categoryID;
late bool? isRelod;
late int? totalRecords;
newspage({this.categoryID, this.isRelod, this.totalRecords});
#override
State<newspage> createState() => _newspageState();
}
class _newspageState extends State<newspage> {
final postcontroller postController = Get.put(postcontroller());
var refreshkey = GlobalKey<RefreshIndicatorState>();
ScrollController _scrollController = new ScrollController();
int _page = 1;
#override
void initState() {
// TODO: implement initState
super.initState();
Future.delayed(Duration.zero, () async {
if (this.widget.isRelod!) {
await postController.fetchPosts(
categoryId: this.widget.categoryID!,
pageNumber: 1,
totalrecords: this.widget.totalRecords!);
}
});
_scrollController.addListener(() async {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
await postController.fetchPosts(
categoryId: this.widget.categoryID!,
pageNumber: ++_page,
totalrecords: this.widget.totalRecords!,
);
}
});
}
#override
Widget build(BuildContext context) {
return newslist();
}
Widget newslist() {
return Container(
child: Obx(() {
if (postController.isloading.value) {
return Center(
child: CircularProgressIndicator(),
);
}
// ignore: curly_braces_in_flow_control_structures
else
// ignore: curly_braces_in_flow_control_structures
return RefreshIndicator(
child: ListView.builder(
key: refreshkey,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: postController.postList.length,
controller: _scrollController,
itemBuilder: (context, index) {
if ((index == postController.postList.length - 1) &&
postController.postList.length <
this.widget.totalRecords!) {
return Center(child: CircularProgressIndicator());
}
return newsbycategory(
model: postController.postList[index]);
}),
onRefresh: () => postController.fetchPosts(
categoryId: this.widget.categoryID!,
pageNumber: ++_page,
totalrecords: this.widget.totalRecords!));
}),
);
}
}

Conditional widget is not working or changing after data load in Flutter

I am just writing a simple application using flutter. I came across a situation where I need to display widgets conditionally. I am fetching data using app script API from Google Sheets. I want to show the "Loading..." text during loading and want to hide it after completing the request.
The following code works as expected if I run it within/using Android Studio. But If I build the APK and install it on my device, only "Loading..." text is showing on the screen, although the data is loaded and stored in listResponse.
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title, required this.baseUrl}) : super(key: key);
final String title;
final String baseUrl;
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late List listResponse = [];
Future categories() async {
var url = widget.baseUrl + 'categories';
http.Response response = await http.get(Uri.parse(url));
if(response.statusCode == 200){
setState(() {
listResponse = json.decode(response.body);
print(listResponse.toString());
});
}
}
#override
void initState(){
categories();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: TopBar(title: widget.title),
body: listResponse.isEmpty ? Center(child: Text('Loading...')) : Center(child: Text(listResponse.toString()))
);
}
}
I have searched for the solution and found some examples on stackoverflow. What could be the issue as the app is running as expected in development. I appreciate the help.
You can use condition value to show loading and result, Take bool isSuccess = false and in your statusCode==200 setState make it isSuccess = true
class _HomePageState extends State<HomePage> {
late List listResponse = [];
bool isSuccess = false;
Future categories() async {
var url = widget.baseUrl + 'categories';
http.Response response = await http.get(Uri.parse(url));
if(response.statusCode == 200){
setState(() {
listResponse = json.decode(response.body);
isSuccess = true;
print(listResponse.toString());
});
}
}
#override
void initState(){
categories();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: TopBar(title: widget.title),
body: isSuccess ? Center(child: Text('Loading...')) : Center(child: Text(listResponse.toString()))
);
}
It will work fine when data is fetched.
Or you can also use FutureBiulder
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: categories(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text(
'There was an error :(',
style: Theme.of(context).textTheme.headline,
);
} else if (snapshot.hasData) {
var count = json.decode(snapshot.data).length;
return Center(child: Text(listResponse.toString()));
} else {
return Center(child: Text('Loading...'))
}
});
}

How do I display an image received from an API response in my app

I am using http package to make get request and fetch data from an API in flutter. But I cannot seem to understand how do I display an image in my app
Here is the code:
***Cars.dart***
Future<CartModel> fetchCartModel() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
if (response.statusCode == 200) {
return CartModel.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load data');
}
}
class Cars extends StatefulWidget {
#override
_CarsState createState() => _CarsState();
}
class _CarsState extends State<Cars> {
Future<CartModel> futureCartModel;
#override
void initState() {
super.initState();
futureCartModel = fetchCartModel();
}
Container(
width: 180,
height: 139,
margin:EdgeInsets.all(5),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(''),
fit: BoxFit.fill,
),
),
),
Here is the model class:
class CartModel {
int albumId;
int id;
String title;
String url;
String thumbnailUrl;
CartModel({this.albumId, this.id, this.title, this.url, this.thumbnailUrl});
CartModel.fromJson(Map<String, dynamic> json) {
albumId = json['albumId'];
id = json['id'];
title = json['title'];
url = json['url'];
thumbnailUrl = json['thumbnailUrl'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['albumId'] = this.albumId;
data['id'] = this.id;
data['title'] = this.title;
data['url'] = this.url;
data['thumbnailUrl'] = this.thumbnailUrl;
return data;
}
}
I was able to get text data from the app but i cannot seem to understand how do I display the image that I am fetching from the API.
The api response is a List of CartModel, not a single CartModel.
You need to use FutureBuilder to fetch api data and show it through ListView.builder and put the thumbnail url for each item into the NetworkImage.
Code:
import 'dart:convert' as convert;
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:stacksolution/cart_model.dart';
class FetchData extends StatefulWidget {
const FetchData({Key? key}) : super(key: key);
#override
_FetchDataState createState() => _FetchDataState();
}
class _FetchDataState extends State<FetchData> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stack Over Flow'),
),
body: FutureBuilder<List<CartModel>>(
future: fetchApiData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (context, index) {
CartModel cartModel = snapshot.data![index];
return Container(
width: 180,
height: 139,
margin: EdgeInsets.all(5),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(cartModel.thumbnailUrl!),
fit: BoxFit.fill,
),
),
);
},
itemCount: snapshot.data!.length,
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
),
);
}
Future<List<CartModel>> fetchApiData() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
if (response.statusCode == 200) {
List<dynamic> list = convert.jsonDecode(response.body);
List<CartModel> cartList =
list.map((e) => CartModel.fromJson(e)).toList();
return cartList;
} else {
throw Exception('Failed to load data');
}
}
}
You can use
Image.network('***your image url here*****')
Correct Your fetchCartModel Function
Future<CartModel> fetchCartModel() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
if (response.statusCode == 200) {
var decodedJson = json.decode(response.body) as List;
return decodedJson.map((e) => CartModel.fromJson(e)).toList();
} else {
throw Exception('Failed to load data');
}
}
and Use FutureBuilder and ListView.builder
class Cars extends StatefulWidget {
const Cars({Key? key}) : super(key: key);
#override
_CarsState createState() => _CarsState();
}
class _CarsState extends State<Cars> {
Future<List<CartModel>> fetchCartModel() async {
var response = await http
.get(Uri.parse("https://jsonplaceholder.typicode.com/photos"));
if (response.statusCode == 200) {
var decodedJson = json.decode(response.body) as List;
return decodedJson.map((e) => CartModel.fromJson(e)).toList();
} else {
return [];
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<CartModel>>(
future: fetchCartModel(),
builder: (_, snapshot) {
if (snapshot.hasData) {
return ListView.builder(itemBuilder: (_, index) {
CartModel cart = snapshot.data![index];
return ListTile(
title: Text(cart.title ?? ""),
leading: Image.network(cart.url.toString()),
);
});
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
} else {
return Center(child: Text("No Data Found"));
}
},
);
}
}

type 'Null' is not a subtype of type 'String' Flutter

I'm trying to get currency conversion data from an API ("https://api.exchangerate.host/latest").
The above error in the title is shown on the screen as a text.
I'm trying to get this message as the output.
"msg":"If you or your company use this project or like what we doing, please consider backing us so we can continue maintaining and evolving this project."
Later the individual currency rates.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http.get(Uri.parse('https://api.exchangerate.host/latest'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final String msg;
Album({
required this.msg,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
msg: json['msg'],
);
}
}
class CurrencyPage extends StatefulWidget {
const CurrencyPage({Key? key}) : super(key: key);
#override
_CurrencyPageState createState() => _CurrencyPageState();
}
class _CurrencyPageState extends State<CurrencyPage> {
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!.msg);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
Any help would be appreciated!
you are using the model incorrectly.
change return Album.fromJson(jsonDecode(response.body)); to return jsonDecode(response.body); and get response data like snapshot.data!['motd']
code:
Future<dynamic> fetchAlbum() async {
final response = await http.get(Uri.parse('https://api.exchangerate.host/latest'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
print(jsonDecode(response.body));
return jsonDecode(response.body);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final String msg;
Album({
required this.msg,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
msg: json['msg'],
);
}
}
class CurrencyPage extends StatefulWidget {
const CurrencyPage({Key? key}) : super(key: key);
#override
_CurrencyPageState createState() => _CurrencyPageState();
}
class _CurrencyPageState extends State<CurrencyPage> {
late dynamic futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<dynamic>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!['motd']['msg']);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
);
}
}
msg key is inside motd object in response. Therefore replace fetchAlbum with this:
Future<Album> fetchAlbum() async {
final response = await http.get(Uri.parse('https://api.exchangerate.host/latest'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var body = jsonDecode(response.body);
return Album.fromJson(body["motd"]);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}

Categories

Resources