setState reloads old value after value change - android

I have a problem with flutter:
Here's my build method for filling TextFields from DB and SendValue method for sending edited values to DB.
After setState TextFields shows old values for a second and then loads the edited values.
This happens after server answer that values had been changed.
What`s the reason of reloading old values?
return Scaffold(
appBar: AppBar(
title: Text('Editing values'),
),
body: Padding(
child: FutureBuilder<List<Photo>>(
future: fetchPhotos(http.Client(), args.id),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? Fields(snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
padding: EdgeInsets.fromLTRB(30, 30, 30, 30),
),
);
Future SendValues(String id) async {
http.Client client;
client = http.Client();
var uri = new Uri.http('localhost', 'edituser.php');
final response =
await client.post(uri, body: {
'id': id,
'name': contname.text,
'surname': contsurname.text,
},
);
if (response.body.contains('OK'))
setState(() {
});
else
print('error');
}

It shows old value because setState is being called after the http request is done, which takes a while.
Don't wait for the response:
Future SendValues(String id) async {
http.Client client;
client = http.Client();
var uri = new Uri.http('localhost', 'edituser.php');
final response = client.post(
uri,
body: {'id': id, 'name': contname.text, 'surname': contsurname.text},
);
setState(() {});
return response;
}

Related

Flutter chat app on Firebase, StreamBuilder reloads blinking and calls on null value when ListView is created

I'm making a FreeLancer app with a chat function. First, I want the chat list that renders chat tiles from Firebase so I use GetX Stream (is this like streamBuilder?) to render the chat tile representing, and in that chat tile (I'm using ListTile) the subtitle text is pulled from FireStore from the latest message using StreamBuilder
Chat List Screen
final user = FirebaseAuth.instance.currentUser;
return user == null
? const Center(
child: Text('Please login to use Chat'),
)
: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () async {
ChatService().addRoom(randomString());
},
child: const Icon(FontAwesomeIcons.solidMessage),
),
appBar: AppBar(
title: const Text('Chats'),
centerTitle: true,
),
body: GetX<ChatController>(
init: ChatController(),
builder: (roomList) {
return ListView.builder(
itemCount: roomList.rooms.length,
itemBuilder: (context, index) {
return ChatTile(roomList.rooms[index]);
},
);
},
),
Chat Tile
final db = FirebaseFirestore.instance.collection('Rooms').doc(room.roomId);
final stream = db.snapshots();
var roomName = 'Loading...';
return StreamBuilder(
stream: stream,
builder: (context, snapshot) {
final roomInfo = snapshot.data as dynamic;
return Column(
children: [
InkWell(
onTap: () {},
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.08,
child: Center(
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
leading: const FlutterLogo(size: 50),
title: Text(roomInfo['roomName']),
subtitle: LastMessage(room.roomId),
trailing: Text(timeago.format(
(roomInfo['lastestMsg'] as Timestamp).toDate())),
),
),
),
),
const Divider()
],
);
},
);
Latest Message
final db = FirebaseFirestore.instance.collection('Rooms').doc(roomId);
final stream = db
.collection('message')
.orderBy('createdDate', descending: true)
.snapshots();
return StreamBuilder<QuerySnapshot>(
stream: stream,
builder: (context, snapshot) {
final messages = snapshot.data!.docs;
final message = messages.first.data() as dynamic;
return Text(
message['content'],
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
});
Chat Room Model
String roomId;
String roomName;
DateTime createDate;
bool isDeleted;
DateTime lastestMsg;
List<String> members;
ChatRoom({
required this.roomId,
required this.roomName,
required this.createDate,
required this.isDeleted,
required this.lastestMsg,
required this.members,
});
factory ChatRoom.fromMap(Map<String, dynamic> map) {
return ChatRoom(
roomId: map['roomId'] as String,
roomName: map['roomName'] as String,
createDate: (map['createDate'] as Timestamp).toDate(),
isDeleted: map['isDeleted'] as bool,
lastestMsg: (map['lastestMsg'] as Timestamp).toDate(),
members: List<String>.from(
(map['members'] as List<dynamic>),
),
);
}
String toJson() => json.encode(toMap());
factory ChatRoom.fromJson(String source) =>
ChatRoom.fromMap(json.decode(source) as Map<String, dynamic>);
Message Model
String senderId;
String content;
DateTime createdDate;
List<String> seenBy;
bool isDeleted;
FreeLanceMessage({
required this.senderId,
required this.content,
required this.createdDate,
required this.seenBy,
required this.isDeleted,
});
factory FreeLanceMessage.fromMap(Map<String, dynamic> map) {
return FreeLanceMessage(
senderId: map['senderId'] as String,
content: map['content'] as String,
createdDate: (map['createdDate'] as Timestamp).toDate(),
seenBy: List<String>.from(
(map['seenBy'] as List<dynamic>),
),
isDeleted: map['isDeleted'] as bool,
);
}
String toJson() => json.encode(toMap());
factory FreeLanceMessage.fromJson(String source) =>
FreeLanceMessage.fromMap(json.decode(source) as Map<String, dynamic>);
Error When the Chat list screen gets mounted to the tree, it only appears for few milliseconds, when I add a new chat, the chat latest message doesn't show an error after this
I use random string so don't mind the title and the msg
Edit: I can use streambuilder and check for connection state but the screen blinks for any data change and I don't want that
so my question is, is this the right way to do this? is there another way? if so, how can I improve this

Flutter The method 'map' was called on null. Receiver: null error

I'm having this super annoying issue of being unable to grab and display a table from my server hosted on PhpmyAdmin. (I've managed to grab the data and have it printed in the console, but now that I'm trying to display it in a table I can't seem to get it working)
I've tried nulling my variables but I'm not really sure what the main culprit for this error is. Any help would be greatly appreciated.
Image of Error
data.dart File
class dataListing extends StatefulWidget {
const dataListing({Key? key}) : super(key: key);
#override
State<dataListing> createState() => _dataListingState();
}
class _dataListingState extends State<dataListing> {
#override
Widget build(BuildContext context) {
return Container();
}
}
class listingData{
String? ListingID, listingName, listingDescription, address, suburbName, phoneNumber, openingHours, Email, Website;
listingData({
this.ListingID,
this.listingName,
this.listingDescription,
this.address,
this.suburbName,
this.phoneNumber,
this.openingHours,
this.Email,
this.Website,
});
//constructor
List<listingData> datalist = [];
factory listingData.fromJSON(Map<String, dynamic> json){
return listingData(
ListingID: json["ListingID"],
listingName: json["listingName"],
listingDescription: json["listingDescription"],
address: json["address"],
suburbName: json["suburbName"],
phoneNumber: json["phoneNumber"],
openingHours: json["openingHours"],
Email: json["Email"],
Website: json["Website"],
);
}
}
Directory.dart file
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:app/pages/data.dart';
class directoryPage extends StatefulWidget {
#override
State<directoryPage> createState() => _directoryPageState();
}
class _directoryPageState extends State<directoryPage> {
// List serviceListing = [];
//
// getAllListing()async{
// String url = "URL HERE";
// var response = await http.get(Uri.parse(url));
// if (response.statusCode == 200){
// setState (() {
// serviceListing = json.decode(response.body);
// });
// print (serviceListing);
// return serviceListing;
// }
// }
bool error = false, dataloaded = false;
var data;
String dataurl = "URL HERE";
#override
void initState (){
loaddata();
super.initState();
// getAllListing();
}
void loaddata() {
Future.delayed(Duration.zero,() async {
var res = await http.post(Uri.parse(dataurl));
if (res.statusCode == 200) {
setState(() {
data = json.decode(res.body);
dataloaded = true;
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Directory'),
centerTitle: true,
elevation: 0,
backgroundColor: Color(0xFFA30B32),
//WSU Appbar Icon
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset("assets/wsulogo.png", scale: 8.0),
),
),
body: Container(
padding: EdgeInsets.all(15),
child:dataloaded?datalist():
Center(
child:CircularProgressIndicator()
),
)
);
}
Widget datalist(){
if(data["error"]) {
return Text(data["errmsg"]);
}else{
List<listingData> datalist = List<listingData>.from(data["data"].map((i){
return listingData.fromJSON(i);
})
);
return Table( //if data is loaded then show table
border: TableBorder.all(width:1, color:Colors.black45),
children: datalist.map((listingdata){
return TableRow( //return table row in every loop
children: [
//table cells inside table row
TableCell(child: Padding(
padding: EdgeInsets.all(5),
child:Text(listingdata.ListingID!)
)
),
TableCell(child: Padding(
padding: EdgeInsets.all(5),
child:Text(listingdata.listingName!)
)
),
TableCell(child: Padding(
padding: EdgeInsets.all(5),
child:Text(listingdata.listingDescription!)
)
),
TableCell(child: Padding(
padding: EdgeInsets.all(5),
child:Text(listingdata.address!)
)
),
]
);
}).toList(),
);
}
}
}
Looks like the issue was actually unrelated to the dart side of things, the php code wasn't properly structuring the data. Cannot have underscores or spaces.
Correct-> $json["dballlisting"] = array (); (I renamed it to just "data" later)
Incorrect->$json["db_all_listing"] = array ();
The error seems to be originating from this line, the data['data'] is null which is expected to be an Array.
List<listingData> datalist = List<listingData>.from(data["data"].map((i){
return listingData.fromJSON(i);
})
You need to investigate your API call to make sure why it is happening. If the null value is expected then you need to add safeguards in your code to make sure it won't break when it encounter such scenarios. You can add null safety checks for that one way to do it would be to
List<listingData> datalist = List<listingData>.from((data["data"] ?? []).map((i){
return listingData.fromJSON(i);
})

App crashing if already one 'await' is active

I have an app for showing world times. I have a page for changing different locations around the world. It's a ListView.
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: colorOne,
appBar: AppBar(
backgroundColor: Colors.black,
title: Text("Change location"),
centerTitle: true,
elevation: 0.0,
),
body: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.fromLTRB(5, 10, 5, 0),
itemCount: locations.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
updateTime(index);
... rest code
As you can see, when I tap on ListTIle, it calls updateTime function
updateTime function:
void updateTime(index) async {
WorldTime instance = locations[index];
await instance.getTime();
Navigator.pop(context, {
"location": instance.location,
"flag": instance.flag,
"time": instance.time,
"date": instance.date,
"isDayTime": instance.isDayTime,
});
// obtain shared preferences
final savingLastLocation = await SharedPreferences.getInstance();
// set value
savingLastLocation.setString("location", instance.location);
savingLastLocation.setString("url", instance.url);
savingLastLocation.setString("flag", instance.flag);
}
If user starts spamming on tiles while awaiting for that function, app will either show full blank grey screen or drop red screen of death saying "boolean expression must be null".
How can I add some kind of loading screen/widget or prevent calling function again if it's already called once?
You can wrap your screen with IgnorePointer, which ignores any click.
Create bool variable.
bool ignore = false;
bool methodcalled = false; // new added line variable
Now wrap your scaffold with IgnorePointer.
return IgnorePointer(
ignoring: ignore,
child: Scaffold(
now, set ignore variable to true when user tap on any item.
onTap: () {
setState(() {
ignore = true;
});
updateTime(index).then((_){
setState(() {
ignore = false;
});
});
.... rest code
Add return in your method.
return 1
void updateTime(index) async {
if(!methodcalled){
methodcalled = !methodcalled;
}else{
return 0;
}
WorldTime instance = locations[index];
await instance.getTime();
Navigator.pop(context, {
"location": instance.location,
"flag": instance.flag,
"time": instance.time,
"date": instance.date,
"isDayTime": instance.isDayTime,
});
// obtain shared preferences
final savingLastLocation = await SharedPreferences.getInstance();
// set value
savingLastLocation.setString("location", instance.location);
savingLastLocation.setString("url", instance.url);
savingLastLocation.setString("flag", instance.flag);
methodcalled = !methodcalled; // added line
return 1; // added line
}
onPressed set like this
onPressed: () async {
dynamic result =
await Navigator.pushNamed(context, '/location');
if (result != null) {
setState(() {
data = {
'location': result['location'],
'flag': result['flag'],
'time': result['time'],
'isDateTime': result['isDateTime']
};
});
}
},

Display list of only product names in flutter, using woocommerce api data in json format

I am able to fetch and print the data in json format in console and I am able to display the entire body of data in list format in the flutter app. But I can't figure out how to display only a specific key and its value in list. Its a list of maps. I have removed the consumer key and secret from this code.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main(){
runApp(MaterialApp(
home: CustomHome(),
));
}
class CustomHome extends StatefulWidget {
#override
_CustomHomeState createState() => _CustomHomeState();
}
class _CustomHomeState extends State<CustomHome> {
List data;
Future<String> getData() async{
var response = await http.get('https://jbaat.com/wp-json/wc/v3/products/?consumer_key=&consumer_secret=');
setState(() {
var converted = jsonDecode(response.body);
data = converted;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
print(data);
return Scaffold(
body: ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('$data'),
),
),
);
}),
);
}
}
Below is the response
[{id: 493, name: Bas 5 Min White Half Sleeve T-Shirt, slug: bas-5-min-white-half-sleeve-t-shirt, permalink: https://www.jbaat.com/product/bas-5-min-white-half-sleeve-t-shirt/, date_created: 2019-12-14T23:39:08, date_created_gmt: 2019-12-14T18:09:08, date_modified: 2019-12-14T23:48:01, date_modified_gmt: 2019-12-14T18:18:01, type: variable, status: publish, featured: false, catalog_visibility: visible, description: , short_description: , sku: , price: 500.00, regular_price: , sale_price: , date_on_sale_from: null, date_on_sale_from_gmt: null, date_on_sale_to: null, date_on_sale_to_gmt: null, price_html: <span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">₹</span>500.00</span>, on_sale: false, purchasable: true, total_sales: 0, virtual: false, downloadable: false, downloads: [], download_limit: -1, download_expiry: -1, external_url: , button_text: , tax_status: taxable, tax_class: , manage_stock: false, stock_quantity: null, stock_status: instock, backorders: no,
Jbaat, I'd recommend creating a model for your response data and use the values accordingly from each item's instance. There are few online converters available which converts your json response to Dart models, here is one - https://javiercbk.github.io/json_to_dart/. Below is a quick example of what it would look like based on your response data,
class Items {
final List<Items> items;
Items({this.items});
factory Items.fromJson(List<dynamic> json) {
List<Items> itemList = json.map((i) => Items.fromJson(i)).toList();
return Items(
items: itemList
);
}
}
class Item {
final int id;
final String name;
......
Item({this.id, this.name,.....});
factory Item.fromJson(Map<String, dynamic> json) {
return Item(
id: json['id'],
name: json['name']
......
);
}
}
And then your getData() would be,
Future<Items> getData() async{
var response = await http.get('https://jbaat.com/wp-json/wc/v3/products/?consumer_key=&consumer_secret=');
return Items.fromJson(json.decode(response.body)).items;
}
You should now have Item list which can be used to get specific item info. You should also use FutureBuilder to call your getData() instead of calling it in initState to make sure data is available before building widgets like so,
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
body: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Item item = snapshot.data[index]; //Your item
return Container(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.name),
),
),
);
}),
);
} else {
return Center(child: CircularProgressIndicator());
}
});
}
Hope this helps. Good luck!
Change the type for data to
List<Map<String,dynamic>>
A possible implementation for your use case:
Map y;
var keytobesearched='name';
List<Map<String,dynamic>> x= [{'id': 493, 'name': 'Bas 5 Min White Half Sleeve T-Shirt', 'slug': 'bas-5-min-white-half-sleeve-t-shirt'}];
x.forEach((Map<String,dynamic> ele){
if(ele.containsKey(keytobesearched))
y=Map.from(ele);
// display/alter y to your liking
});
If you would want a complete plug and play Woocommerce Sdk that handles authentication, products, customer, shipping etc for you, you can use the Woo Commerce SDK library for flutter at https://pub.dev/packages/woocommerce
Woocommerce myWoocommerce = WooCommerce(baseUrl: yourBaseUrl, consumerKey: yourConsumerKey, consumerSecret: consumerSecret);
Then simply get your lists eg:
WooProduct products = await myWocommerce.getProducts(); // returns the ist of products.
for (var product in products){ print(product.name)};

Flutter- Make the app show the previously fetched data when device is offline

I'm new to Flutter and need a bit of help. I've built a random joke generator app that reads data from the API and displays a new joke every time a button is pressed. I want to make the app show the previously fetched data when device is offline. I tried searching online but found nothing that does it using Flutter.
class _HomePageState extends State<HomePage> {
List data;
Future<Jokes> post;
String url="https://official-joke-api.appspot.com/random_joke";
var response;
Future<Jokes> getData() async {
response =
await http.get(url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
changeApi()
{
setState(() {
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
});
}
#override
void initState()
{
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
final key = new GlobalKey<ScaffoldState>();
// TODO: implement build
return Scaffold(
key: key,
backgroundColor: Colors.amberAccent,
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new FutureBuilder<Jokes>(
future:
getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//checks if the response returns valid data
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new GestureDetector(
child: new Text(
snapshot.data.setup ,
style: TextStyle(fontFamily: "Rock Salt"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.setup, ));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
/
SizedBox(
height: 10.0,
),
new GestureDetector(
child: new Text(
" - ${snapshot.data.punchline}",
style: TextStyle(fontFamily: "Roboto"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.punchline));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
],
),
);
} else if (snapshot.hasError) {
//checks if the response throws an error
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
SizedBox(
height: 25.0,
),
new RaisedButton(
onPressed: changeApi,
color: Colors.pinkAccent,
child: Text("Press for a new joke", style: TextStyle(color: Colors.white,)),
)
],
),
),
);
}
}
class Jokes {
final String setup;
final String punchline;
Jokes({this.setup, this.punchline});
factory Jokes.fromJson(Map<String, dynamic> json) {
return Jokes(setup: json['setup'], punchline: json['punchline']);
}
}
Api
Here's my full code: code
There are some videos about caching, here's the one from flutter team, and one from tensor programming channel.
You can use connectivity plugin to check whether the device is offline.
If device is offline, show data from shared_preferences or sqflite, if it's online, fetch new data (and of course update your cache).

Categories

Resources