I'm experimenting with flutter, creating a small screen to fetch cryptocurrency data. I have three crypto name (BTC, ETH, LTC), each will be displayed on card, the rate on USD.
I can get the data for each currency, and display it. But in my code, if each data fetch took 1 second, the cards display will be updated after 3 seconds. All data will be fetched first, then screen updated using setState().
This is the initial working code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'coin_data.dart' as coinData;
class PriceScreen extends StatefulWidget {
#override
_PriceScreenState createState() => _PriceScreenState();
}
class _PriceScreenState extends State<PriceScreen> {
String _selectedCurrency = "USD";
List<coinData.CoinData> _coinData = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Coin Ticker'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
buildCoinCards(),
],
),
);
}
Card buildCoinCard(coinData.CoinData currentCoinData) {
return Card(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 28.0),
child: Text(
'1 ${currentCoinData.coinName()} = ${currentCoinData.last} $_selectedCurrency',
),
),
);
}
buildCoinCards() {
List<Card> coinCards = [];
for (var currentCoinData in _coinData) {
coinCards.add(
buildCoinCard(currentCoinData),
);
}
return Column(
children: coinCards,
);
}
#override
void initState() {
super.initState();
for (String crypto in coinData.cryptoList) {
_coinData.add(
coinData.CoinData(
last: 0,
displaySymbol: "$crypto-$_selectedCurrency",
),
);
}
updateCoinData();
}
void updateCoinData() async {
var coinDataFetch =
await coinData.CoinData().getAllCoinData(_selectedCurrency);
setState(() {
_coinData = coinDataFetch;
});
}
}
I'd like to update each item everytime a HTTP call is success. So the card will be updated one by one (took 1 second each) instead of waiting for 3 seconds. So I change the updateCoinData() to this, but it's not working.
void updateCoinData() async {
for (coinData.CoinData currentCoinData in _coinData) {
var fetchCoinData = await currentCoinData.getCoinData(
currentCoinData.coinName(), _selectedCurrency);
setState(() {
currentCoinData = fetchCoinData;
print(
currentCoinData.coinName() + " " + currentCoinData.last.toString());
});
}
}
I suppose I cannot update the List item on setState()?
How do I achieve this update one-by-one?
Thanks
Related
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);
})
I have a screen in flutter SearchFoodItemPage It is a stateful widget in a file named search_food_item_page.dart.
My purpose is to fetch list of items from firebase and display it on this screen.
I want to fetch data from firebase when this screen starts. I want to do all the data fetching in this file.
For that I tried fetching data in build method of the the widget. But we cannot add async modifier to the build method hence it did not work. I would like to know where to build the data source for this purpose.
Below is the code snippet for this screen.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'; // new
class SearchFoodItemPage extends StatefulWidget {
SearchFoodItemPage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_SearchFoodItemPageState createState() => _SearchFoodItemPageState();
}
class _SearchFoodItemPageState extends State<SearchFoodItemPage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
List<String> _adsList = [];
/////////////////////////////////////////////////////////////// code for building data source
print("****************************************************************************");
await FirebaseFirestore.instance
.collection('ads')
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
print(doc["_iname"]);
_adsList.add( doc["_iname"] as String );
});
});
print('${_adsList.length}');
for (final foodname in _adsList) {
print('${foodname.toString()}');
}
///////////////////////////////////////////////////////////////
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// AdvertisementForm(),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Thanks!
You're looking for a FutureBuilder as shown in the FlutterFire documentation on reading data using get and the example of using a ListView in that same page:
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance.collection('ads').get(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data() as Map<String, dynamic>;
return new ListTile(
title: new Text(data['_iname']),
);
}).toList(),
);
}
return Text("loading");
},
);
}
I'm working on a Flutter project where I want to pre-cache images at the start of the app.
The idea is when you start the app the first time, it downloads a list of images either cache / stored in DB / stored in local storage / or any other viable solution. I don't really know the best practice here. And then when you start the app the next time you already have the photos so you don't want to download them again (based on the version of the backend data).
From what I saw;
The cache, I don't really know if it is not persistent enough and I don't know if I'll have enough control over it.
The local storage, I think I'll have to ask the user permission to access the files of the device
The database, I'll have to encode/decode the photos every time I want to save/get them so it'll take some computation.
My ideal choice would be the database as I'd have control over the data and it's a rather small app so the computation is minimal and I won't have to ask the user permission.
I've tried over the last days to implement this using every of the solution stated above and I can't make it work.
Right now I want to store an Image into the database (I'm using sqflite) without displaying it and then read it to display it as a Widget from another Screen. I have 2 screens, the first one that I called SplashScreen to fetch and save the images and the second one which is HomeScreen to read the images from the database and display them.
SplashScreen.dart:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
bool hasInternet = true;
var subscription;
double loading = 0;
#override
initState() {
super.initState();
getPhotos();
subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult connectivityResult) {
setState(() {
hasInternet = connectivityResult == ConnectivityResult.mobile || connectivityResult == ConnectivityResult.wifi;
});
});
}
dispose() {
super.dispose();
subscription.cancel();
}
Future getPhotos() async {
List<String> photoUrls = await fetchPhotos();
photoUrls.asMap().forEach((index, photoUrl) async {
var response = await http
.get(photoUrl);
loading = index / photoUrls.length;
// Convert Photo response and save them in DB
imageDBFormat = ...
savePhotosInDB(imageDBFormat)
});
}
#override
Widget build(BuildContext context) {
if(loading == 1) {
Navigator.pushNamed(context, '/');
}
return Center(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/back.png"),
fit: BoxFit.cover,
)
),
child: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 300,
child: LinearProgressIndicator(
value: loading,
valueColor: AlwaysStoppedAnimation<Color>(ProjectColors.primaryDark),
),
)
],
),
],
)
),
),
);
}
}
HomeScreen.dart:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
List<Widget> images;
initState() {
super.initState();
getImages();
}
void getImages() async {
List imgs = getImagesFromDB();
setState(() {
images = imgs.map((image) {
// Convert imgs from db into Widget
Widget imageWidget = ...
return Container(
child: imageWidget,
);
}).toList();
});
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/back.png"),
fit: BoxFit.cover,
)
),
child: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Column(
children: images == null ? images : <Widget>[],
),
)
);
}
}
I'm OK to reconsider any point to follow the best practices.
Thanks a lot for your help.
You can use https://pub.dev/packages/cached_network_image library,it's using sqlite as a storage already and you can configure duration of persistance.
class CustomCacheManager extends BaseCacheManager {
static const key = "customCache";
static CustomCacheManager _instance;
factory CustomCacheManager() {
if (_instance == null) {
_instance = new CustomCacheManager._();
}
return _instance;
}
CustomCacheManager._() : super(key,
maxAgeCacheObject: Duration(months: 6),
maxNrOfCacheObjects: 100);
Future<String> getFilePath() async {
var directory = await getTemporaryDirectory();
return p.join(directory.path, key);
}
}
and after that you can use in your build method:
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
cacheManager: CustomCacheManager()
),
I'm building a selectable checkbox contact list in flutter but if a contact has only an email and no number, an error is thrown. I want to create a loop to add a number of '99999' to the contacts phone number if they don't have one. Please can someone guide me with an explanation of what I should change to make this work? I have had a go, but I am quite new to flutter so I'm not completely certain on syntax etc...
Here is the part of the code that I am trying to put the function into.
setMissingNo()async {
Iterable<Contact> contactsToLoop = (await ContactsService.getContacts()).toList();
contactsToLoop.forEach((Contact) { contactsToLoop = []..add(Item.fromMap({'label': 'work', 'value': 99999})); });
}
//fetch contacts from setMissingNo
getAllContacts() async{
Iterable<Contact> _contacts = (await ContactsService.getContacts()).toList();
setState(() {
contacts = _contacts;
}
);
}
Here is my whole code
import 'package:flutter/material.dart';
// TODO: make it ask for permissions otherwise the app crashes
import 'package:contacts_service/contacts_service.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Contact> contacts = [];
List<Contact> contactsFiltered = [];
TextEditingController searchController = new TextEditingController();
#override
void initState() {
super.initState();
getAllContacts();
searchController.addListener(() => filterContacts());
}
//remove the +'s and spaces from a phone number before its searched
String flattenPhoneNumber(String phoneStr) {
return phoneStr.replaceAllMapped(RegExp(r'^(\+)|\D'), (Match m) {
return m[0] == "+" ? "+" : "";
});
}
//loop and set all contacts without numbers to 99999, pass new list to getAllContacts
setMissingNo()async {
Iterable<Contact> contactsToLoop = (await ContactsService.getContacts()).toList();
contactsToLoop.forEach((Contact) { contactsToLoop = []..add(Item.fromMap({'label': 'work', 'value': 99999})); });
}
//fetch contacts from setMissingNo
getAllContacts() async{
Iterable<Contact> _contacts = (await ContactsService.getContacts()).toList();
setState(() {
contacts = _contacts;
}
);
}
//filtering contacts function to match search term
filterContacts() {
List<Contact> _contacts = [];
_contacts.addAll(contacts);
if (searchController.text.isNotEmpty) {
_contacts.retainWhere((contact) {
String searchTerm = searchController.text.toLowerCase();
String searchTermFlatten = flattenPhoneNumber(searchTerm);
String contactName = contact.displayName.toLowerCase();
bool nameMatches = contactName.contains(searchTerm);
if (nameMatches == true) {
return true;
}
if (searchTermFlatten.isEmpty) {
return false;
}
var phone = contact.phones.firstWhere((phn) {
String phnFlattened = flattenPhoneNumber(phn.value);
return phnFlattened.contains(searchTermFlatten);
}, orElse: () => null);
return phone != null;
});
setState(() {
contactsFiltered = _contacts;
});
}
}
final selectedContacts = Set<Contact>();
#override
Widget build(BuildContext context) {
bool isSearching = searchController.text.isNotEmpty;
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
AppBar(
title: Text('Create Group'),
),
Container(
child: TextField(
controller: searchController,
decoration: InputDecoration(
labelText: 'Search Contacts',
border: OutlineInputBorder(
borderSide: new BorderSide(
color: Theme.of(context).primaryColor
)
),
prefixIcon: Icon(
Icons.search,
color: Theme.of(context).primaryColor
)
),
),
),
Expanded( child: ListView.builder(
shrinkWrap: true,
itemCount: isSearching == true ? contactsFiltered.length : contacts.length,
itemBuilder: (context, index) {
Contact contact = isSearching == true ? contactsFiltered[index] : contacts[index];
//TODO: make it so when you clear your search, all items appear again & when you search words it works
return CheckboxListTile(
title: Text(contact.displayName),
subtitle: Text(
contact.phones.elementAt(0).value
),
value: selectedContacts.contains(contact),
onChanged: (bool value) {
if (value) {
selectedContacts.add(contact);
} else {
selectedContacts.remove(contact);
}
setState((){});
// TODO: add in function to add contact ID to a list
});
},
),
/*new Expanded(
child: Align(
alignment: Alignment.bottomLeft,
child: BottomNavigationBar(
currentIndex: _currentIndex,
items: const <BottomNavigationBarItem>[
//TODO: create new contact functionality to add someone by name + email
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text('Add Contact'),
),
BottomNavigationBarItem(
icon: Icon(Icons.create),
title: Text('Create Group'),
),
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
}
)
)
)*/
)
],
)
),
);
}
}
Updating all contacts listed on the device might take a long time depending on the size of the device's contact list. Add that the task is being done on the UI thread. You may want to consider using Isolate for the task to move away the load from the UI thread. If you can also provide the errors that you're getting, it'll help us get a picture of the issue.
Another thing is, the way you're approaching the issue might be impractical. Is it really necessary to write a placeholder phone number to the contacts? The issue likely stems from trying to fetch the number from a contact but the Object returns null. Perhaps you may want to consider only updating the phone number when the contact is selected.
I would like to make an endlessFeed in Fluter but the app terminates without giving me any information why. Basically it happens after I scrolled down about 60 images then it starts to lag a bit and it crashes.
I tested another API but the same there. It uses images with lower resolution so it takes longer to scroll down until it stops working.
So I don't know what happens here. My guess is that there are to many images in the ListView so that the phone can't handle it and crashes.
I've put the whole code below because I don't even know wehere the problem could be. Is there maybe another way to achieve an endlessImageFeed?
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:cached_network_image/cached_network_image.dart';
// my base url
String imageUrl = "http://192.168.2.107:8000";
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'EndlessFeed',
theme: ThemeData(
primaryColor: Colors.white,
),
home: PhotoList(),
);
}
}
class PhotoList extends StatefulWidget {
#override
PhotoListState createState() => PhotoListState();
}
class PhotoListState extends State<PhotoList> {
StreamController<Photo> streamController;
List<Photo> list = [];
#override
void initState() {
super.initState();
streamController = StreamController.broadcast();
streamController.stream.listen((p) => setState(() => list.add(p)));
load(streamController);
}
load(StreamController<Photo> sc) async {
// URL for API
String url = "http://192.168.2.107:8000/api/";
/* ______________________________________________
I also tried another API but it chrashes also (but it takes longer until crash):
String url = "https://jsonplaceholder.typicode.com/photos/";
______________________________________________ */
var client = new http.Client();
var req = new http.Request('get', Uri.parse(url));
var streamedRes = await client.send(req);
streamedRes.stream
.transform(UTF8.decoder)
.transform(json.decoder)
.expand((e) => e)
.map((map) => Photo.fromJsonMap(map))
.pipe(sc);
}
#override
void dispose() {
super.dispose();
streamController?.close();
streamController = null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("EndlessFeed"),
),
body: Center(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) => _makeElement(index),
),
),
);
}
Widget _makeElement(int index) {
if (index >= list.length) {
return null;
}
return Container(
child: Padding(
padding: EdgeInsets.only(top: 20.0),
child: Column(
children: <Widget>[
child: new Container(
// my base URL + one image
child: new Image(image: new CachedNetworkImageProvider(imageUrl + list[index].mImage))
),
),
],
),
));
}
}
class Photo {
final String mImage;
Photo.fromJsonMap(Map map)
: mImage= map['mImage'];
}