I recently started Flutter. I want to make a simple contacts application with "mysql1" but I failed to pull the data. I installed the MySQL plugin in VS Code and I can see the table I created, but the application always returns "no data". After some research, I learned that it is better to do it with the API. But I still want to know why this is not working. Thank you.
Model: contact_model.dart
class ContactModel {
int? id;
String? name;
String? lastName;
String? phoneNumber;
ContactModel(this.id, this.name, this.lastName, this.phoneNumber);
}
View: homepage.dart
import 'package:flutter/material.dart';
import '../models/contact_model.dart';
import '../../products/utility/database_helper.dart';
import 'add_data.dart';
// ignore: must_be_immutable
class HomePage extends StatefulWidget {
bool state = false;
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final DatabaseOperations _databaseOperations = DatabaseOperations();
List<ContactModel> allData = [];
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("IContacts"),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
const Duration(seconds: 3);
// ignore: avoid_print
print("value");
Navigator.push<bool>(
context, MaterialPageRoute(builder: (context) => data_add()));
},
),
body: Container(
child: allData.isNotEmpty
? ListView.builder(
itemCount: allData.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(allData[index].name ?? "error"),
subtitle: Text(allData[index].phoneNumber ?? "error"),
leading: Text(allData[index].id.toString()),
trailing: IconButton(
tooltip: "Delete Person",
icon: const Icon(Icons.delete),
onPressed: () async {}),
);
})
: const Text("no data")),
);
}
void fetchData() async {
allData = await _databaseOperations.fetchData();
setState(() {});
}
}
DatabaseHelper: database_helper.dart
import 'package:mysql1/mysql1.dart';
import '../../feature/models/contact_model.dart';
class DatabaseOperations {
final String _host = 'localhost';
final int _port = 3306;
final String _user = 'root';
final String _password = '';
final String _db = 'contact_app';
DatabaseOperations();
Future fetchData() async {
try {
final connect = await MySqlConnection.connect(ConnectionSettings(
host: _host, port: _port, user: _user, password: _password, db: _db));
List<ContactModel> myList = [];
var dataList = await connect.query('SELECT * FROM `contacts_db`');
for (var item in dataList) {
myList.add(ContactModel(
item["id"], item["name"], item["lastName"], item["phoneNumber"]));
}
await connect.close();
return myList;
} catch (e) {}
}
Future<bool> addData(
{required String name,
required String lastName,
required String phoneNumber}) async {
try {
final connect = await MySqlConnection.connect(
ConnectionSettings(
host: _host,
port: _port,
user: _user,
password: _password,
db: _db),
);
await connect.query(
"insert into contacts_db (name,lastName,phoneNumber) values (?,?,?)",
[name, lastName, phoneNumber]);
await connect.close();
return true;
} catch (e) {
return false;
}
}
Future<bool> updateData(
{required int id,
required String name,
required String lastName,
required String phoneNumber}) async {
try {
final connect = await MySqlConnection.connect(
ConnectionSettings(
host: _host,
port: _port,
user: _user,
password: _password,
db: _db),
);
await connect.query(
"update contacts_db set name=? , lastName=? , phoneNumber = ? where id = ?",
[name, lastName, phoneNumber, id]);
await connect.close();
return true;
} catch (e) {
return false;
}
}
Future<bool> deleteData({required int id}) async {
try {
final connect = await MySqlConnection.connect(
ConnectionSettings(
host: _host,
port: _port,
user: _user,
password: _password,
db: _db),
);
await connect.query('delete from contacts_db where id=?', [id]);
await connect.close();
return true;
} catch (e) {
return false;
}
}
}
Debug Console :
E/flutter (27691): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: type 'Null' is not a subtype of type 'List<ContactModel>'
E/flutter (27691): #0 _HomePageState.fetchData
package:rehber_uygulamasi/…/view/homepage.dart:58
E/flutter (27691): <asynchronous suspension>
E/flutter (27691):
Application finished.
Exited (sigterm)
**_HomePageSate.fetchData package:rehber_uygulamasi...homepage.dart:58 :
void fetchData() async {
allData = await _databaseOperations.fetchData();
setState(() {});
}
My assumption: Your fetchData() method is throwing an exception. You are catching it, but not doing anything with it. When throwing the exception your method basically returns nothing, hence the error is saying type 'Null' is not a subtype of type 'List<ContactModel>'.
Related
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) {
Guys how to transfer data when I login.. For example when I login the transfer data are User ID, email and password.. So if I want to add something that requires the user id how do I call it?
This is my login method
void login() async {
if(passController.text.isNotEmpty && emailController.text.isNotEmpty) {
var response = await http.post(
Uri.parse("url"),
body: ({
'email': emailController.text,
'password': passController.text,
}));
if (response.statusCode == 200) {
final body = jsonDecode(response.body);
print(body.toString());
List<dynamic> data = body["Details"];
print(data[0]['email']);
pageRoute(data[0]['name']);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Wrong password")));
}
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Sila isi tempat kosong")));
}
}
void pageRoute(data) async {
//STORE VALUE IN SHARED PREFERENCES
SharedPreferences pref = await SharedPreferences.getInstance();
await pref.setString("login", data);
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => nav()),
(route) => false);
This is where I want to take the user id without put it on the textfield.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import '../Utils/lib.dart';
class CarList extends StatefulWidget {
const CarList({Key? key}) : super(key: key);
#override
State<CarList> createState() => _CarListState();
}
class _CarListState extends State<CarList> {
var userController = TextEditingController();
// late SharedPreferences logindata;
// String? Email;
final pref = Pref();
// late String name;
// late String registeration_no;
#override
void initState() {
// TODO: implement initState
super.initState();
initial();
}
void initial() async {
if (pref.isLogin()) {
final body = jsonDecode(pref.getLogin());
print(body['Details']['user_id'].toString());
}
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.all(10),
child: TextField(
controller: userController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
suffixIcon: Icon(Icons.email),
labelText: 'User ID',
),
),
),
Container(
margin: EdgeInsets.all(25),
child: TextButton(
child: Text('test',
style: TextStyle(fontSize: 20.0,
color: Colors.blueAccent,
backgroundColor: Colors.white),
),
onPressed: () {
list();
},
),
)
]
)
);
}
void list() async {
{
var response = await http.post(
Uri.parse("url"),
body: ({
'user_id': userController.text,
}));
if (response.statusCode == 200) {
final body = jsonDecode(response.body);
// final SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
// sharedPreferences.setString('email', emailController.text);
pref.saveLogin(true, response.body);
print(body.toString());
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Successfully Login")));
}
}
}
}
Im kinda new with this flutter so might thankful with your help
You can do something like this:
This is store data in pageRoute.
void login() async {
if (passController.text.isNotEmpty && emailController.text.isNotEmpty) {
var response = await http.post(
Uri.parse("http://servisjer.me-tech.com.my/api/Login"),
body: ({
'email': emailController.text,
'password': passController.text,
}));
if (response.statusCode == 200) {
final body = jsonDecode(response.body);
print(body.toString());
List<dynamic> data = body["Details"];
print(data[0]['email']);
pageRoute(Data(data[0]['email'], data[0]['user_id']));
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Wrong password")));
}
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Sila isi tempat kosong")));
}
}
void pageRoute(Data data) async {
MySharedPreferences().storeUser(data);
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => nav()), (route) => false);
}
MySharedPreferences
class MySharedPreferences {
Future<void> storeUser(Data data) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("email", data.email);
prefs.setInt("user_id", data.userId);
}
Future getUserId() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var userId = prefs.getInt("user_id");
return userId;
}
}
In CarList, retrieve data using this way
#override
void initState() {
MySharedPreferences().getUserId().then((value) {
setState(() {
userController.text = value.toString();
});
});
//TODO: IMPLEMENT INITSTATE
super.initState();
}
Data
class Data {
var email;
var userId;
Data(this.email,this.userId);
}
I am passing the authentication token from Auth.dart file to Products.dart file to enable the app to fetch the Products in my database but the app is not able to fetch those,
I am very new to flutter any help would be appreciated
Thank you
here is my Auth.dart file
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import '../models/http_exceptions.dart';
class Auth with ChangeNotifier {
String _token;
DateTime _expiryDate;
String _userId;
bool get isAuth {
return token != null;
}
String get token {
if (_expiryDate != null &&
_expiryDate.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
Future<void> _authenticate(
String email, String password, String urlSegment) async {
final url = Uri.parse(
'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=<key>',
);
try {
final response = await http.post(
url,
body: json.encode(
{
'email': email,
'password': password,
'returnSecureToken': true,
},
),
);
final responseData = json.decode(response.body);
if (responseData['error'] != null) {
throw HttpEception(responseData['error']['message']);
}
_token = responseData['idToken'];
_userId = responseData['localId'];
_expiryDate = DateTime.now().add(
Duration(
seconds: int.parse(responseData['expiresIn']),
),
);
notifyListeners();
} catch (error) {
throw error;
}
}
Future<void> signup(String email, String password) async {
return _authenticate(email, password, 'signUp');
}
Future<void> login(String email, String password) async {
return _authenticate(email, password, 'signInWithPassword');
}
}
Here is my Products.dart file, which has the Product class which is
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shop_app/models/http_exceptions.dart';
class Product with ChangeNotifier {
final String id;
final String title;
final String description;
final double price;
final String imageUrl;
bool isFavorite;
Product({
#required this.id,
#required this.title,
#required this.description,
#required this.price,
#required this.imageUrl,
this.isFavorite = false,
});
Future<void> toggleFavoriteStatus() async {
final url = Uri.https(
'<confedential>.firebaseio.com',
'/products/$id.json',
);
final oldStatus = isFavorite;
isFavorite = !isFavorite;
notifyListeners();
final response = await http.patch(
url,
body: json.encode(
{
'isFavorite': isFavorite,
},
),
);
if (response.statusCode >= 400) {
isFavorite = oldStatus;
notifyListeners();
throw HttpEception('Could Not Change To Favourite');
}
}
}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/http_exceptions.dart';
import './product.dart';
class Products with ChangeNotifier {
List<Product> _items = [];
final String authToken;
Products(this.authToken,this._items);
List<Product> get items {
return [..._items];
}
List<Product> get favoriteItems {
return _items.where((prodItem) => prodItem.isFavorite).toList();
}
Product findById(String id) {
return _items.firstWhere((prod) => prod.id == id);
}
Future<void> fetchAndSetProducts() async {
final url = Uri.https(
'<details>.firebaseio.com',
'/products.json?auth=$authToken',
);
try {
final response = await http.get(url);
// print(json.decode(response.body));
final extractedData = json.decode(response.body) as Map<String, dynamic>;
if (extractedData == null) {
return;
}
final List<Product> loadedProducts = [];
extractedData.forEach((productId, productData) {
loadedProducts.add(
Product(
id: productId,
title: productData['title'],
description: productData['description'],
price: productData['price'],
isFavorite: productData['isFavourite'],
imageUrl: productData['imageUrl'],
),
);
});
_items = loadedProducts;
notifyListeners();
} catch (error) {
throw error;
}
}
Future<void> addProduct(Product product) async {
var url = Uri.https(
'<details>.firebaseio.com',
'/products.json',
);
try {
final response = await http.post(
url,
body: json.encode(
{
'title': product.title,
'description': product.description,
'imageUrl': product.imageUrl,
'price': product.price,
'isFavourite': product.isFavorite,
},
),
);
final newProduct = Product(
title: product.title,
description: product.description,
price: product.price,
imageUrl: product.imageUrl,
id: json.decode(response.body)['name'],
);
_items.add(newProduct);
notifyListeners();
} catch (error) {
throw error;
}
}
Future<void> updateProduct(String id, Product newProduct) async {
final prodIndex = _items.indexWhere((prod) => prod.id == id);
if (prodIndex >= 0) {
final url = Uri.https(
'<details>.firebaseio.com',
'/products/$id.json',
);
http.patch(
url,
body: json.encode(
{
'title': newProduct.title,
'description': newProduct.description,
'imageUrl': newProduct.imageUrl,
'price': newProduct.price,
},
),
);
_items[prodIndex] = newProduct;
notifyListeners();
} else {
print('...');
}
}
Future<void> deleteProduct(String id) async {
final url = Uri.https(
'<details>.firebaseio.com',
'/products/$id.json',
);
final exisitingProductIndex = _items.indexWhere((prod) => prod.id == id);
var exisitingProduct = _items[exisitingProductIndex];
_items.removeAt(exisitingProductIndex);
notifyListeners();
final response = await http.delete(url);
if (response.statusCode >= 400) {
_items.insert(exisitingProductIndex, exisitingProduct);
notifyListeners();
throw HttpEception('Could Not Delete Product.');
}
exisitingProduct = null;
}
}
And finally the main.dart file which uses all this stuff
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './providers/auth.dart';
import './screens/cart_screen.dart';
import './screens/products_overview_screen.dart';
import './screens/product_detail_screen.dart';
import './providers/products.dart';
import './providers/cart.dart';
import './providers/orders.dart';
import './screens/orders_screen.dart';
import './screens/user_products_screen.dart';
import './screens/edit_product_screen.dart';
import './screens/auth_screen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
// Defining these providers for lisening to changes in the data
providers: [
ChangeNotifierProvider.value(
value: Auth(),
),
// First parameter is the type of data that we depened on and second is the data that we arre passing
ChangeNotifierProxyProvider<Auth, Products>(
update: (ctx, auth, previousProducts) => Products(
auth.token,
previousProducts == null ? [] : previousProducts.items,
),
),
ChangeNotifierProvider.value(
value: Cart(),
),
ChangeNotifierProvider.value(
value: Orders(),
),
],
child: Consumer<Auth>(
builder: (context, auth, _) => MaterialApp(
title: 'MyShop',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
fontFamily: 'Lato',
),
// home: ProductsOverviewScreen(),
home: auth.isAuth ? ProductsOverviewScreen() : AuthScreen(),
routes: {
ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
CartScreen.routeName: (ctx) => CartScreen(),
OrdersScreen.routeName: (ctx) => OrdersScreen(),
UserProductsScreen.routeName: (ctx) => UserProductsScreen(),
EditProductScreen.routeName: (ctx) => EditProductScreen(),
},
),
),
);
}
}
Just added create into ChangeNotifierProxyProvider
ChangeNotifierProxyProvider<Auth, Products>(
create: (_)=>Products('',[]),
update: (ctx, auth, previousProducts) => Products(
auth.token,
previousProducts == null ? [] : previousProducts.items,
),
),
and changed the url string in Products.dart file
Uri url = Uri.parse(
'https://<some_details>.firebaseio.com/products.json?auth=$authToken');
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
I have a list of items being populated from an async call in a Stateful Widget. It grabs the first item of the list (which should really be just one item) and displays content from it in a card.
When I navigate to the screen, it says there is no element meets[0] with a big bright red screen, but the screen almost immediately goes away (in ~.5 seconds). I'm assuming that this means that the call is finishing and the error disappears. This however makes for a very poor user experience when they see an error randomly.
I've tried putting the async call in the init method before build - however, the error is still happening.
RESPONSE PAGE:
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:location/location.dart';
import 'package:marketapp/Areas/Meet/MeetDetailsPage.dart';
class ResponsePage extends StatefulWidget {
static const int = 5;
static const routeName = '/responsepage';
final ResponsePageArguments rpa;
ResponsePage({Key key, #required this.rpa}) : super(key: key);
// Create initial state of the Meet Page
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return ResponsePageState();
}
}
class ResponsePageState extends State<ResponsePage> {
Location location = Location();
LocationData currentLocation;
var isLoading = false;
var id;
var meets = new List<MeetModel>();
#override
Widget build(BuildContext context) {
// TODO: implement build
final _formKey = GlobalKey<FormState>();
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Card(
child: ListTile(
title: Text(meets[0].host + " wants to meet you!"),
subtitle: Text("Requested at " + meets[0].requestTime),
)),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 5.0),
child: Row(children: <Widget>[
RaisedButton(
onPressed: () {
declineRequest(id);
},
child: Text('Decline')),
RaisedButton(
onPressed: () {
// Validate returns true if the form is valid, false otherwise
acceptRequest(id);
},
child: Text('Accept'),
),
]))
]));
}
#override
initState() {
getMeet(widget.rpa.id);
id = widget.rpa.id;
super.initState();
location.onLocationChanged().listen((value) {
if (this.mounted) {
setState(() {
currentLocation = value;
});
}
});
}
acceptRequest(id) async {
setState(() {
isLoading = true;
});
final url = 'https://.azurewebsites.net/Meets/MeetResponse/$id';
var userToken = await getToken();
print("Token printed " + userToken.toString());
var body = jsonEncode({'latitude': currentLocation.latitude.toString(),
'longitude' : currentLocation.longitude.toString()});
print("Current Latitude: " + currentLocation.latitude.toString());
print("Current Longitude: " + currentLocation.longitude.toString());
/*final responseBody = (await http.get(
'https://.azurewebsites.net/Auths/RegisterDevice?deviceToken=$deviceToken&userToken=$userToken'))
.body;*/
/*return */
http.post(
url,
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer $userToken"
},
body: body
).then((http.Response response) {
print("Accept Response Status: ${response.statusCode}");
print("Accept Response Body: ${response.body}");
setState(() {
isLoading = false;
});
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
MeetDetailsPage(mdpa: MeetDetailsPageArguments(id))));
});
}
declineRequest(id) async {
setState(() {
isLoading = true;
});
final url = 'https://.net/Meets/MeetResponse/$id';
var userToken = await getToken();
print("Token printed " + userToken.toString());
var body = jsonEncode({'userToken': '$userToken'});
/*final responseBody = (await http.get(
'https://.net/Auths/RegisterDevice?deviceToken=$deviceToken&userToken=$userToken'))
.body;*/
/*return */
http.post(
url,
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer $userToken"
},
/*body: body*/
).then((http.Response response) {
print("Decline Response Status: ${response.statusCode}");
print("Decline Response Body: ${response.body}");
setState(() {
isLoading = false;
});
var responseBody = response.body;
});
}
getMeet(id) async {
setState(() {
isLoading = true;
});
final url = 'https://``````````````.net/Meets/Get/$id';
var userToken = await getToken();
print("Token printed " + userToken.toString());
var body = jsonEncode({'userToken': '$userToken'});
/*final responseBody = (await http.get(
'https://``````````.net/Auths/RegisterDevice?deviceToken=$deviceToken&userToken=$userToken'))
.body;*/
/*return */
http.get(
url,
headers: {"Authorization": "Bearer $userToken"},
/*body: body*/
).then((http.Response response) async {
print("Get Response Status: ${response.statusCode}");
print("Get Response Body: ${response.body}");
setState(() {
isLoading = false;
});
var responseBody = response.body;
meets = MeetModel.fromJsonList(json.decode(responseBody));
});
}
}
Future<String> getToken() async {
final storage = new FlutterSecureStorage();
return await storage.read(key: 'token');
}
class ResponsePageArguments {
String id;
ResponsePageArguments(this.id);
}
// Code below is useless
class MeetModel {
String meetId;
String host;
String hostId;
String address;
String city;
String zip;
String category;
String latitude;
String longitude;
String product;
String productId;
String reference;
String hostPlaceId;
String venueAddress;
String venuePlaceId;
String venueLatitude;
String venueLongitude;
String recipient;
String recipientAddress;
String recipientZip;
String recipientCity;
String recipientPlaceID;
String recipientLatitude;
String recipientLongitude;
String requestTime;
String responseTime;
String recipientId;
String isArchived;
String meetType;
MeetModel.fromJson(obj) {
this.meetId = obj['MeetId'].toString();
this.host = obj['Host'].toString();
this.address = obj['Address'].toString();
this.city = obj['City'].toString();
this.zip = obj['Zip'].toString();
this.category = obj['Category'].toString();
this.latitude = obj['Latitude'].toString();
this.longitude = obj['Longitude'].toString();
this.product = obj['Product'].toString();
this.productId = obj['ProductId'].toString();
this.reference = obj['Reference'].toString();
this.hostPlaceId = obj['HostPlaceId'].toString();
this.venueAddress = obj['VenueAddress'].toString();
this.venuePlaceId = obj['VenuePlaceId'].toString();
this.venueLatitude = obj['VenueLatitude'].toString();
this.venueLongitude = obj['VengueLongitude'].toString();
this.recipient = obj['Recipient'].toString();
this.recipientAddress = obj['RecipientAddress'].toString();
this.recipientZip = obj['RecipientZip'].toString();
this.recipientPlaceID = obj['RecipientPlaceID'].toString();
this.recipientLatitude = obj['RecipientLatitude'].toString();
this.recipientLongitude = obj['RecipientLongitude'].toString();
this.requestTime = obj['RequestTime'].toString();
this.responseTime = obj['ResponseTime'].toString();
this.hostId = obj['HostId'].toString();
this.recipientId = obj['RecipientId'].toString();
this.isArchived = obj['IsArchived'].toString();
this.meetType = obj['MeetType'].toString();
}
static List<MeetModel> fromJsonList(jsonList) {
return jsonList.map<MeetModel>((obj) => MeetModel.fromJson(obj)).toList();
}
}
MEETS PAGE:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:marketapp/Areas/Meet/ResponsePage.dart';
import 'MeetDetailsPage.dart';
class MeetsPage extends StatefulWidget {
#override
State createState() => new MeetsPageState();
}
class MeetsPageState extends State<MeetsPage> {
var meets = new List<MeetModel>();
var isLoading = false;
_fetchData() async {
setState(() {
isLoading = true;
});
final url = 'https://.azurewebsites.net/Meets/GetAll';
var userToken = await getToken();
print("Token printed " + userToken.toString());
var body = jsonEncode({'userToken': '$userToken'});
/*final responseBody = (await http.get(
'https://.azurewebsites.net/Auths/RegisterDevice?deviceToken=$deviceToken&userToken=$userToken'))
.body;*/
/*return */
http.post(
url,
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer $userToken"
},
/*body: body*/
).then((http.Response response) {
print("GetAll Response Status: ${response.statusCode}");
print("GetAll Response Body: ${response.body}");
setState(() {
isLoading = false;
});
var responseBody = response.body;
meets = MeetModel.fromJsonList(json.decode(responseBody));
});
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
/*appBar: AppBar(
title: Text("Meet List"),
backgroundColor: Colors.indigo,
),*/
body: isLoading
? Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: meets.length,
itemBuilder: (context, index) {
if (meets[index].host == "xxxxxx#gmail.com") { // Do not hard code email.
return ListTile(
onTap: () {
if (meets[index].venueAddress != "null") {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MeetDetailsPage(
mdpa: MeetDetailsPageArguments(
meets[index].meetId))));
} else if (meets[index].venueAddress == "null") {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ResponsePage(
rpa: ResponsePageArguments(
meets[index].meetId))));
}
},
title: Text(
meets[index].recipient,
style: TextStyle(fontWeight: FontWeight.w500)),
trailing: Icon(
Icons.map,
color: Colors.blue[500],
),
subtitle: Text(meets[index].requestTime),
);
} else {
return ListTile(
onTap: () {
if (meets[index].venueAddress != "null") {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MeetDetailsPage(
mdpa: MeetDetailsPageArguments(
meets[index].meetId))));
} else if (meets[index].venueAddress == "null") {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ResponsePage(
rpa: ResponsePageArguments(
meets[index].meetId))));
}
},
title: Text(
meets[index].host,
style: TextStyle(fontWeight: FontWeight.w500)),
trailing: Icon(
Icons.map,
color: Colors.blue[500],
),
subtitle: Text(meets[index].requestTime),
);
}
},
));
}
#override
void initState() {
_fetchData();
super.initState();
}
}
class MeetModel {
String meetId;
String host;
String hostId;
String address;
String city;
String zip;
String category;
String latitude;
String longitude;
String product;
String productId;
String reference;
String hostPlaceId;
String venueAddress;
String venuePlaceId;
String venueLatitude;
String venueLongitude;
String recipient;
String recipientAddress;
String recipientZip;
String recipientCity;
String recipientPlaceID;
String recipientLatitude;
String recipientLongitude;
String requestTime;
String responseTime;
String recipientId;
String isArchived;
String meetType;
MeetModel.fromJson(obj) {
this.meetId = obj['MeetId'].toString();
this.host = obj['Host'].toString();
this.address = obj['Address'].toString();
this.city = obj['City'].toString();
this.zip = obj['Zip'].toString();
this.category = obj['Category'].toString();
this.latitude = obj['Latitude'].toString();
this.longitude = obj['Longitude'].toString();
this.product = obj['Product'].toString();
this.productId = obj['ProductId'].toString();
this.reference = obj['Reference'].toString();
this.hostPlaceId = obj['HostPlaceId'].toString();
this.venueAddress = obj['VenueAddress'].toString();
this.venuePlaceId = obj['VenuePlaceId'].toString();
this.venueLatitude = obj['VenueLatitude'].toString();
this.venueLongitude = obj['VengueLongitude'].toString();
this.recipient = obj['Recipient'].toString();
this.recipientAddress = obj['RecipientAddress'].toString();
this.recipientZip = obj['RecipientZip'].toString();
this.recipientPlaceID = obj['RecipientPlaceID'].toString();
this.recipientLatitude = obj['RecipientLatitude'].toString();
this.recipientLongitude = obj['RecipientLongitude'].toString();
this.requestTime = obj['RequestTime'].toString();
this.responseTime = obj['ResponseTime'].toString();
this.hostId = obj['HostId'].toString();
this.recipientId = obj['RecipientId'].toString();
this.isArchived = obj['IsArchived'].toString();
this.meetType = obj['MeetType'].toString();
}
static List<MeetModel> fromJsonList(jsonList) {
return jsonList.map<MeetModel>((obj) => MeetModel.fromJson(obj)).toList();
}
}
Future<String> getToken() async {
final storage = new FlutterSecureStorage();
return await storage.read(key: 'token');
}
class Backend {
static Future<List<MeetModel>> GetAll(userToken) async {
final url = 'https://.net/Auths/RegisterDevice';
var body = jsonEncode({'userToken': '$userToken'});
/*final responseBody = (await http.get(
'https://.azurewebsites.net/Auths/RegisterDevice?deviceToken=$deviceToken&userToken=$userToken'))
.body;*/
return http
.post(url,
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer $userToken"
},
body: body)
.then((http.Response response) {
print("GetAll Response Status: ${response.statusCode}");
print("GetAll Response Body: ${response.body}");
var responseBody = response.body;
return MeetModel.fromJsonList(json.decode(responseBody));
});
}
}
The error still appears, but goes away when the async call finishes. Note - this error happens when you select an item from the list of meets - it navigates to ResponsePage, and for a brief second, there is a flash of red indicating that there is not enough items in the list. This error disappears when the call finishes with the correct form.
Is there a way to stop building the card until the data in the list of meets finishes getting populated?