in want create table from api response.i created data table using future builder. The future builder fires api twice not not return any data
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: unpaid(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
padding: const EdgeInsets.all(5),
child:
Dataclass(datalist: snapshot.data as List<UnpaidDetails>));
} else {
return Container(
// padding: const EdgeInsets.all(5),
// child: Dataclass(
// datalist: snapshot.data as List<UnpaidDetails>)
);
}
},
),
);
}
Future<String?> getToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('token');
}
Future<List<UnpaidDetails>> unpaid() async {
String? token = await getToken();
final response = await http.get(
Uri.parse('https://test.url/api/unpaid'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
});
if (response.statusCode == 200) {
var result = jsonDecode(response.body);
List jsonResponse = result["UnpaidDetails"] as List;
return jsonResponse.map((e) => UnpaidDetails.fromJson(e)).toList();
} else {
throw Exception('Failed to update album.');
}
}
}
class Dataclass extends StatefulWidget {
Dataclass({Key? key, required List<UnpaidDetails> this.datalist})
: super(key: key);
List<UnpaidDetails> datalist;
#override
DataclassState createState() {
return DataclassState();
}
}
class DataclassState extends State<Dataclass> {
#override
Widget build(BuildContext context)
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: FittedBox(
child: DataTable(
sortColumnIndex: 1,
showCheckboxColumn: false,
border: TableBorder.all(width: 1.0),
columns: const [
DataColumn(
label: Text(
"Period",
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
)),
DataColumn(
label: Text(
"Amount",
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
)),
],
rows: widget.datalist
.map((data) => DataRow(cells: [
DataCell(
Text(data.month,
style: const TextStyle(
fontSize: 26, fontWeight: FontWeight.w500)),
),
DataCell(
Text(data.price.toString(),
style: const TextStyle(
fontSize: 26, fontWeight: FontWeight.w500)),
),
]))
.toList(),
))); }}
1 api call twice
api call fires twice the first one bring response. second one is send request without header and receive error. i am using jsontodart.in to create class. Help me execute future builder in clean way
For the non data problem :
Remove your getter :
get datalist => null;
declare a datalist variable in your Datatable class:
class Dataclass extends StatefulWidget {
Dataclass({Key? key, required List<UnpaidDetails> this.datalist})
: super(key: key);
List<UnpaidDetails> datalist;
And read it from your state with
rows: widget.datalist
instead of
rows: datalist
And for your second call. You don't have to make a call in initstate. So you can delete your initstate method and change your future builder like this :
body: FutureBuilder(
future: unpaid(),
I have an app which gets data from the backend. I used a FutureBuilder and for the most classes when i try to pass the variable that im getting data, it's successfully being send to the other classes and i use the variable. But in another case i try to pass the variable to the other class and it's failing saying that null is being returned from the api.
return Scaffold(
body: FutureBuilder<Response>(
future: futureDataForStatus,
builder: (context, snapshot) {
if (snapshot.hasData) {
WorkingLocationStatus accountInfo = WorkingLocationStatus.fromJson(
json.decode(snapshot.data!.body),
);
sendAccountInfo(){
return IsUserWorking(accountStatus: accountInfo.status,);
}
-------------
class IsUserWorking extends StatelessWidget {
const IsUserWorking({
Key? key,
this.accountStatus,
}) : super(key: key);
final String? accountStatus; // according to debugger its returning null
#override
Widget build(BuildContext context) {
if (accountStatus == 'NOT_WORKING') {
return const WorkingScreen();
}
return const StopWorkingScreen();
}
}
------
// what the api returns
when the user is not working api returns this
{"status":"NOT_WORKING"}
but if the user is working it returns this
{"status":"WORKING","name":{"locationName":"Gjakova e Re","location":"Rruga Besmir Haxhi Koci, nr 577","startTime":"2022-02-28T21:16:38.510879+01:00","endTime":null,"duration":"PT10.256862755S"}}
// same variable passing to other classes
return Scaffold(
body: FutureBuilder<Response>(
future: futureDataForStatus,
builder: (context, snapshot) {
if (snapshot.hasData) {
WorkingLocationStatus accountInfo = WorkingLocationStatus.fromJson(
json.decode(snapshot.data!.body),
);
LocationNameData(accountInfo: accountInfo), //works fine
LocationData(accountInfo: accountInfo), // works fine
WorkingStartTime(accountInfo: accountInfo), // works fine
class LocationNameData extends StatelessWidget {
const LocationNameData({
Key? key,
required this.accountInfo,
}) : super(key: key);
final WorkingLocationStatus accountInfo;
#override
Widget build(BuildContext context) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 30),
child: RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: [
const TextSpan(
text: 'Status: ',
style: TextStyle(
fontSize: 18,
color: Color(0xFF616161),
),
),
TextSpan(
text: accountInfo.status, // works just fine
style: const TextStyle(
fontSize: 18,
color: Colors.green,
fontWeight: FontWeight.bold),
),
],
),
),
),
),
],
);
// function to fetch the data
Future<Response> getLocationStatus(BuildContext context) async {
final navigator = GlobalKey<NavigatorState>();
SharedPreferences prefs = await SharedPreferences.getInstance();
String? authorization = prefs.getString('authorization');
var url = 'url';
locationStatus = await http.get(
Uri.parse(url),
headers: <String, String>{
'authorization': authorization ?? basicAuth.toString(),
"Content-Type": "application/json"
},
);
print('qqqqqqqqqqqq${locationStatus!.statusCode}');
return locationStatus!;
}
I'am trying to create a Chat app. All the messages are fetching from the api as list. Here the problem is it shows the new message only if i reload the page. I don't want like this. I want it like when the new message is added to server it should show on the screen without reloading the page(like chating apps). How to listen for new messages and show in the screen. Here i using listview.builder to show all the messages. I think that i want to use StreamBuilder for this functionality. How can i change this below code to this feature. The complete code is added below.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class MessageScreen extends StatefulWidget {
const MessageScreen({Key? key}) : super(key: key);
#override
_MessageScreenState createState() => _MessageScreenState();
}
class _MessageScreenState extends State<MessageScreen> {
List allMessages = [];
TextEditingController message = TextEditingController();
void getAllMessages() async {
var url = '$baseurl/getMessages.php';
var response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
var jsonData = json.decode(response.body);
setState(() {
allMessages = jsonData;
});
}
}
#override
void initState() {
super.initState();
getAllMessages();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Chat'),
),
body: Column(
children: [
Container(
height: 500,
child: ListView.builder(
itemCount: allMessages.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
allMessages[index]['user'],
),
subtitle: Text(
allMessages[index]['message'],
),
);
},
),
),
Container(
child: Column(
children: [
TextField(
controller: message,
),
MaterialButton(
onPressed: (){
//Code to send message
},
child: const Text('SEND'),
)
],
),
)
],
),
);
}
}
The response from the api is
[{user:john#gmail.com,message:Hello},{user:rose#gmail.com,message:How are you},{user:rahul#gmail.com,message:What u all doing},{user:rose#gmail.com,message:Lets go for a trip}]
You can use provider state management and move your function
void getAllMessages() async {
var url = '$baseurl/getMessages.php';
var response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
var jsonData = json.decode(response.body);
setState(() {
allMessages = jsonData;
});
}
}
to provider class and make a watch variable for allmessages use in listview.builder.
please refer provider documentation.
Displaying the data from my API based on the Dropdown selected value. I want to display on the same page. The data from the server(response) is displaying on the console. But still, this data is not displaying.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
//import 'package:json_parsing_example/model2.dart';
//import 'package:json_parsing_example/models.dart'
List<YouModel> youModelFromJson(String str) => List<YouModel>.from(json.decode(str).map((x) => YouModel.fromJson(x)));
String youModelToJson(List<YouModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class YouModel {
String columnName;
YouModel({
this.columnName,
});
factory YouModel.fromJson(Map<String, dynamic> json) => YouModel(
columnName: json["column_name"],
);
Map<String, dynamic> toJson() => {
"column_name": columnName,
};
}
UserModel userModelFromJson(String str) => UserModel.fromJson(json.decode(str));
String userModelToJson(UserModel data) => json.encode(data.toJson());
class UserModel {
String username;
String name;
UserModel({
this.username,
this.name,
});
factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
username: json["username"],
name: json["Name"],
);
Map<String, dynamic> toJson() => {
"username": username,
"Name": name,
};
}
class Addoffers2 extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Addoffers2State();
}
class _Addoffers2State extends State<Addoffers2> {
List<String> _companies = [];
bool _isLoading = false;
String _selectedCompany;
#override
void initState() {
super.initState();
_selectedCompany=null;
_getcompanylist();
}
Future<String> loadFromAssets() async {
return await rootBundle.loadString('json/parse.json');
}
_getcompanylist() async {
setState(() {
_isLoading = true;
});
print("getting..");
final responseStr =
await http.get('http://10.0.2.2/Flutter/GetCompanieslist.php');
//String responseStr = await loadFromAssets();
final listData = youModelFromJson(responseStr.body);
for(int i=0;i<listData.length;i++)
{
print('this is the list :'+listData[i].columnName);
// _companies.add(listData[i].columnName);
}
// above method is the standard method to get creating a model class and then get the list of strings
// I have just shown you but example is according to you code .
// this above loadFromAssets is that you hit the api and you get the json string response
// i have created a dummy json file where i can the String.
// Else everything is the same as below you just have to pass the response.body to the json.decode method.
var jsonData = json.decode(responseStr.body);
for (var u in jsonData) {
_companies.add(u.toString().substring(14, u.toString().length - 1));
}
for (int i = 0; i < _companies.length; i++) {
print(_companies[i].toString());
}
setState(() {
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
//double width = MediaQuery.of(context).size.width;
//double height = MediaQuery.of(context).size.height;
return MaterialApp(
//color: Colors.red,
home: Scaffold(
backgroundColor: Colors.red,
appBar: AppBar(
backgroundColor: Theme.of(context).backgroundColor,
title: Text("Add.."),
),
body: Container(
color: Colors.blue,
// just put your height i have modified it replace it by height / 8
child: _isLoading
? CircularProgressIndicator()
: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
//MainAxisAlignment: MainAxisAlignment.spaceBetween,
Text('Choose..'),
DropdownButtonHideUnderline(
child: DropdownButton(
// hint: Text('Choose Company'), // Not necessary for Option 1
value: _selectedCompany,
onChanged: (newValue) {
setState(() {
_selectedCompany = newValue;
// here i have taken the boolen variable to show and hide the list if you have not seleted the value from the dropdown the it will show the text and if selected the it will show you the list.
});
print(_selectedCompany);
},
items: _companies.map((company) {
return DropdownMenuItem(
child: new Text(company.toString()),
value: company,
);
}).toList(),
),
),
],
),
),
),
// this is to to check for the initial if string is null then show the text widget.
// else if the value is selected then it will show the listview
_selectedCompany == null
? Text('Select the dropdown value for list to appear.')// sample text you can modify
: Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
height: 100,
color: Theme.of(context).backgroundColor,
child: new FutureBuilder(
future: _getUsers(
_selectedCompany), // a Future<String> or null
builder: (BuildContext context,
AsyncSnapshot snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Container(
child: Center(
child: new CircularProgressIndicator(
backgroundColor: Colors.white,
),
));
}
if (snapshot.hasError) {
return Center(
child: new Text(
'Error ${snapshot.error}'),
);
} else {
return Center(
child: Padding(
padding: const EdgeInsets.fromLTRB(
5.0, 8.0, 5.0, 8.0),
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:
(BuildContext context,
int index) {
List<UserModel> user =
snapshot.data;
var username =
user[index].username;
var stuname =
user[index].name;
print(
'This is the user name :$username');
print(
'This is the name : $stuname');
//var title=snapshot.data[index]["Title"];
// new Text(parsedDate.toString());
return StudentList2(
regdNo: username,
name: stuname);
}),
),
);
}
}),
),
),
],
)),
)),
);
}
}
Future<String> loadFromAssets2() async {
return await rootBundle.loadString('json/parse2.json');
}
// the above method is just for the sample purpose where you get you json String after hitting the api call for _getUsers method
Future<List<UserModel>> _getUsers(String selectedcompany) async {
// here you call you api and you get the response
var url = 'https://10.0.2.2/Flutter/getstudentdata.php;
var data = { 'company': selectedcompany};
// Starting Web Call with data.
var response = await http.post(url, body: json.encode(data));
print(response.body);
//String responseStr = await loadFromAssets2();
final userModel = userModelFromJson(response.body);
// I have just made the model class for where fromt he below you get the complete object and then added to the list and returned.
List<UserModel> users = [];
users.add(userModel);
print('This is the name : ${users[0].name}'); // Even this also not getting printed
return users;
}
class StudentList2 extends StatefulWidget {
final regdNo;
final name;
const StudentList2({
Key key,
this.regdNo,
this.name,
}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<StudentList2> {
bool visible = false;
#override
Widget build(BuildContext context) {
print(widget.regdNo.toString());
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: new Card(
color: Theme.of(context).primaryColor,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 2.0),
child: Container(
child: new Text(
widget.regdNo.toUpperCase(),
style: TextStyle(
color: Colors.yellowAccent,
fontWeight: FontWeight.bold,
fontSize: 15.0,
),
),
),
),
ListTile(
title: new Text(
widget.regdNo,
style: TextStyle(
color: Colors.black,
fontSize: 14.0,
),
),
subtitle: new Text(
(widget.name),
style: TextStyle(
color: Colors.black,
fontSize: 15.0,
),
),
),
//
],
)),
);
}
}
I am able to retrieve the data from the server and print it on the console. Still, the data is not displaying. I do not know where I did the mistake.
So I have completely updated the answer and there are many things that you don't follow according to the global standard.
So I have listed some of the key things that you should follow :
Following is you company list json :
[
{
"column_name": "ABC"
},
{
"column_name": "XYZ"
}
]
Following is the get user json that you will get :
{"username":"1111","Name":"ABC" }
And Later the model class I have create accordingly to the json that you provided and then you can create your own based in the added json.
There are Two model classes that I have created :
First model class is for the company :
// To parse this JSON data, do
//
// final youModel = youModelFromJson(jsonString);
import 'dart:convert';
List<YouModel> youModelFromJson(String str) => List<YouModel>.from(json.decode(str).map((x) => YouModel.fromJson(x)));
String youModelToJson(List<YouModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class YouModel {
String columnName;
YouModel({
this.columnName,
});
factory YouModel.fromJson(Map<String, dynamic> json) => YouModel(
columnName: json["column_name"],
);
Map<String, dynamic> toJson() => {
"column_name": columnName,
};
}
second mode class is for the user :
// To parse this JSON data, do
//
// final userModel = userModelFromJson(jsonString);
import 'dart:convert';
UserModel userModelFromJson(String str) => UserModel.fromJson(json.decode(str));
String userModelToJson(UserModel data) => json.encode(data.toJson());
class UserModel {
String username;
String name;
UserModel({
this.username,
this.name,
});
factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
username: json["username"],
name: json["Name"],
);
Map<String, dynamic> toJson() => {
"username": username,
"Name": name,
};
}
Below is the main ui file just Check the comments that I have made so that it will be helpful for you .
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:json_parsing_example/model2.dart';
import 'package:json_parsing_example/models.dart';
void main() => runApp(Addoffers());
class Addoffers extends StatefulWidget {
#override
State<StatefulWidget> createState() => _AddoffersState();
}
class _AddoffersState extends State<Addoffers> {
List<String> _companies = [];
bool _isLoading = false;
String _selectedCompany;
#override
void initState() {
super.initState();
_selectedCompany=null;
_getcompanylist();
}
Future<String> loadFromAssets() async {
return await rootBundle.loadString('json/parse.json');
}
_getcompanylist() async {
setState(() {
_isLoading = true;
});
print("getting..");
/* final response =
await http.get('http://10.0.2.2/Flutter/GetCompanieslist.php'); */
String responseStr = await loadFromAssets();
final listData = youModelFromJson(responseStr);
for(int i=0;i<listData.length;i++)
{
print('this is the list :'+listData[i].columnName);
// _companies.add(listData[i].columnName);
}
// above method is the standard method to get creating a model class and then get the list of strings
// I have just shown you but example is according to you code .
// this above loadFromAssets is that you hit the api and you get the json string response
// i have created a dummy json file where i can the String.
// Else everything is the same as below you just have to pass the response.body to the json.decode method.
var jsonData = json.decode(responseStr);
for (var u in jsonData) {
_companies.add(u.toString().substring(14, u.toString().length - 1));
}
for (int i = 0; i < _companies.length; i++) {
print(_companies[i].toString());
}
setState(() {
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
//double width = MediaQuery.of(context).size.width;
//double height = MediaQuery.of(context).size.height;
return MaterialApp(
//color: Colors.red,
home: Scaffold(
backgroundColor: Colors.red,
appBar: AppBar(
backgroundColor: Theme.of(context).backgroundColor,
title: Text("Add.."),
),
body: Container(
color: Colors.blue,
// just put your height i have modified it replace it by height / 8
child: _isLoading
? CircularProgressIndicator()
: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
//MainAxisAlignment: MainAxisAlignment.spaceBetween,
Text('Choose..'),
DropdownButtonHideUnderline(
child: DropdownButton(
// hint: Text('Choose Company'), // Not necessary for Option 1
value: _selectedCompany,
onChanged: (newValue) {
setState(() {
_selectedCompany = newValue;
// here i have taken the boolen variable to show and hide the list if you have not seleted the value from the dropdown the it will show the text and if selected the it will show you the list.
});
print(_selectedCompany);
},
items: _companies.map((company) {
return DropdownMenuItem(
child: new Text(company.toString()),
value: company,
);
}).toList(),
),
),
],
),
),
),
// this is to to check for the initial if string is null then show the text widget.
// else if the value is selected then it will show the listview
_selectedCompany == null
? Text('Select the dropdown value for list to appear.')// sample text you can modify
: Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
height: 100,
color: Theme.of(context).backgroundColor,
child: new FutureBuilder(
future: _getUsers(
_selectedCompany), // a Future<String> or null
builder: (BuildContext context,
AsyncSnapshot snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Container(
child: Center(
child: new CircularProgressIndicator(
backgroundColor: Colors.white,
),
));
}
if (snapshot.hasError) {
return Center(
child: new Text(
'Error ${snapshot.error}'),
);
} else {
return Center(
child: Padding(
padding: const EdgeInsets.fromLTRB(
5.0, 8.0, 5.0, 8.0),
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:
(BuildContext context,
int index) {
List<UserModel> user =
snapshot.data;
var username =
user[index].username;
var stuname =
user[index].name;
print(
'This is the user name :$username');
print(
'This is the name : $stuname');
//var title=snapshot.data[index]["Title"];
// new Text(parsedDate.toString());
return StudentList2(
regdNo: username,
name: stuname);
}),
),
);
}
}),
),
),
],
)),
)),
);
}
}
Future<String> loadFromAssets2() async {
return await rootBundle.loadString('json/parse2.json');
}
// the above method is just for the sample purpose where you get you json String after hitting the api call for _getUsers method
Future<List<UserModel>> _getUsers(String selectedcompany) async {
/* var data = await http.post("http://10.0.2.2/Flutter/getstdata.php", body: {
"company": selectedcompany,
//print(data.body);
}); */
// here you call you api and you get the response
String responseStr = await loadFromAssets2();
final userModel = userModelFromJson(responseStr);
// I have just made the model class for where fromt he below you get the complete object and then added to the list and returned.
List<UserModel> users = [];
users.add(userModel);
print('This is the name : ${users[0].name}');
//final x=users.length.toString();
//debugPrint("records:" + users.length.toString());
//debugPrint("kkk:" + absentees.length.toString());
return users;
}
class StudentList2 extends StatefulWidget {
//MyHomePage(String branch);
final regdNo;
final name;
const StudentList2({
Key key,
this.regdNo,
this.name,
}) : super(key: key);
//final String branch;
//const StudentList({Key key, this.branch}) : super(key: key);
//MyHomePage(String branch);
// final String title;
// final String branch="";
// MyHomePage(String branch, {Key key, this.title}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<StudentList2> {
bool visible = false;
//bool _btnEnabled = false;
//bool _validate = false;
// var _firstPress = true ;
//Color _iconColor = Colors.yellow;
//Color _iconColor2 = Colors.white;
//var poll;
//DateTime parsedDate;
#override
Widget build(BuildContext context) {
print(widget.regdNo.toString());
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: new Card(
color: Theme.of(context).primaryColor,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 2.0),
child: Container(
child: new Text(
widget.regdNo.toUpperCase(),
style: TextStyle(
color: Colors.yellowAccent,
fontWeight: FontWeight.bold,
fontSize: 15.0,
),
),
),
),
ListTile(
title: new Text(
widget.regdNo,
style: TextStyle(
color: Colors.black,
fontSize: 14.0,
),
),
subtitle: new Text(
(widget.name),
style: TextStyle(
color: Colors.black,
fontSize: 15.0,
),
),
),
//
],
)),
);
}
}
// This is not the good approach to create a model class just check the sample model class that i have created.
class User {
//final int index;
final String username;
final String name;
//final Float cgpa;
User(
this.username,
this.name,
);
}
And below is the sample Gif file for you :
As stated by #pskink the method _getcompanylist() is async. An async function runs asynchronously, which means that the rest of the program doesn't wait for it to complete. You can use a future builder to deal whit that or you can simply wait for it by using the await function. I believe that for your code snippet future builder is the better choice.
I was trying to fetch data from my backend which is developed using Laravel framework. I need to use POST request for each request to pass the API middleware.
When I use GET request without headers, future builder works just fine and got updated immediately after the data on the backend has changed but I can't get Laravel to grab the current authenticated user because no token provided.
But when I use POST request or GET request with headers, it stops updating and I need to switch to another page and go back in order to get the changes.
Please take a look at my script below:
Future<List<Document>> fetchDocuments(http.Client http, String token) async {
final headers = {'Authorization': 'Bearer $token'};
final response = await http.post(
'http://192.168.1.2:8000/api/documents/all',
headers: headers,
);
// Use the compute function to run parseDocuments in a separate isolate.
return compute(parseDocuments, response.body);
}
// A function that converts a response body into a List<DOcument>
List<Document> parseDocuments(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Document>((json) => Document.fromJSON(json)).toList();
}
class Document {
final int id;
final String name;
Document({this.id, this.name});
factory Document.fromJSON(Map<String, dynamic> json) {
return Document(
id: json['id'] as int,
name: json['name'] as String,
);
}
}
class StudentDocumentScreen extends StatefulWidget {
StudentDocumentScreen({Key key}) : super(key: key);
_StudentDocumentScreenState createState() => _StudentDocumentScreenState();
}
class _StudentDocumentScreenState extends State<StudentDocumentScreen> {
final storage = FlutterSecureStorage();
final _uploadURL = 'http://192.168.1.2:8000/api/documents';
final _scaffoldKey = GlobalKey<ScaffoldState>();
final SnackBar uploadingSnackbar = SnackBar(
content: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Uploading file, this may take a while...',
style: TextStyle(color: Colors.white),
),
),
);
final SnackBar successSnackbar = SnackBar(
content: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'File uploaded!',
style: TextStyle(color: Colors.white),
),
),
backgroundColor: Colors.green,
);
final SnackBar errorSnackbar = SnackBar(
content: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Oops... Something went wrong!',
style: TextStyle(color: Colors.white),
),
),
backgroundColor: Colors.red,
);
String _token;
String _path;
#override
void initState() {
super.initState();
_getToken();
}
void _getToken() async {
String token = await storage.read(key: 'accessToken');
setState(() => _token = token);
}
void _openFileExplorer() async {
try {
_path = null;
_path = await FilePicker.getFilePath();
} on PlatformException catch (e) {
print('Unsupported operation');
print(e);
}
if (!mounted) return;
if (_path != null || _path.isNotEmpty) {
_uploadDocument(_path);
}
}
void _uploadDocument(String path) async {
_scaffoldKey.currentState.showSnackBar(uploadingSnackbar);
try {
var multipartFile = await http.MultipartFile.fromPath('document', path);
var request = http.MultipartRequest('POST', Uri.parse(_uploadURL));
request.headers.addAll({'Authorization': 'Bearer $_token'});
request.files.add(multipartFile);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
_scaffoldKey.currentState.showSnackBar(successSnackbar);
} else {
_scaffoldKey.currentState.showSnackBar(errorSnackbar);
}
} catch (e) {
print('Error when uploading files');
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Koleksi Dokumen'),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _openFileExplorer,
),
);
}
Widget _buildBody() {
return FutureBuilder<List<Document>>(
future: fetchDocuments(http.Client(), _token),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? DocumentList(documents: snapshot.data)
: Center(child: CircularProgressIndicator());
},
);
}
}
class DocumentList extends StatelessWidget {
final List<Document> documents;
const DocumentList({Key key, this.documents}) : super(key: key);
#override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: documents.length,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Icon(
Icons.insert_drive_file,
color: Colors.black12,
size: 50.0,
),
),
Text(
documents[index].name,
textAlign: TextAlign.center,
),
],
),
),
);
},
);
}
}
And below is the laravel script:
# in the `api.php`
Route::post('documents/all', 'DocumentController#index');
# in the `DocumentController`
public function index()
{
$userId = auth()->user()->id;
return Document::whereUserId($userId)
->get()
->load('user');
}
Any idea? Thanks in advance.
I just found a temporary solution, putting setState(() {}); after uploading progress do the job.
Please feel free to answer if you guys has a better one.
Solution:
void _uploadDocument(String path) async {
_scaffoldKey.currentState.showSnackBar(uploadingSnackbar);
try {
var multipartFile = await http.MultipartFile.fromPath('document', path);
var request = http.MultipartRequest('POST', Uri.parse(_uploadURL));
request.headers.addAll({'Authorization': 'Bearer $_token'});
request.files.add(multipartFile);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
_scaffoldKey.currentState.showSnackBar(successSnackbar);
setState(() {}); // reload state
} else {
_scaffoldKey.currentState.showSnackBar(errorSnackbar);
}
} catch (e) {
print('Error when uploading files');
print(e);
}
}