How to fix flutter widget flickering? - android

I'm trying to load an image from firebase storage to my app however I have this weird issue where the profile page(where this image is loading) keeps flickering. The image is loading fine however the whole widget keeps flickering. I have narrowed the issue down to the setState() called within the function getProfilePic() after some debugging, however I do not know if it's the function itself or my call to said function.
P.S there is no issue with the fileURL or picRef.getDownloadURL() as I've tested this with a random internet image as well and got the same flickering.
class profileTab extends StatefulWidget {
#override
_profileTabState createState() => _profileTabState();
}
class _profileTabState extends State<profileTab> {
User user = FirebaseAuth.instance.currentUser;
String _image = "https://picsum.photos/250?image=9";
Reference picRef = FirebaseStorage.instance.ref().child(FirebaseAuth.instance.currentUser.uid);
Future<Widget> getProfilePic() async {
await picRef.getDownloadURL().then((fileURL){
setState(() {
_image = fileURL;
});
});
}
#override
Widget build(BuildContext context) {
getProfilePic();
return StreamBuilder(
stream: FirebaseFirestore.instance.collection('users').doc(user.uid).snapshots(),
builder: (context, snapshot){
if (snapshot.connectionState == ConnectionState.active){
return ListView(
children: <Widget>[
SizedBox(height: 100.0,),
CircleAvatar(
radius: 100.0,
backgroundColor: Colors.lightBlueAccent,
child: ClipOval(
child: SizedBox(
width: 180.0,
height: 180.0,
child: Image.network(_image,fit: BoxFit.fill,),
),
),
),
SizedBox(height: 30.0,),
Center(child: Text("Name: " + snapshot.data.data()['name'],textScaleFactor: 3.0,)),
]
);
}
else {
return CircularProgressIndicator();
}
},
);
}
}

getProfilePic is redrawing widget by calling setState.
setState calls build method which calls getProfilePic.
Therefore, when first time build method is called we call getProfilePic which again updates widget tree.
Fix: Inside getProfilePic add check to call setState if _image is null which will redraw widget only once.
It would be better if you use Image.network. You can refer this
https://www.woolha.com/tutorials/flutter-display-image-from-network-url-show-loading

Related

Flutter: ListView.builder + MultiImagePicker2

Im currently working on an app where it gets the pictures from galleries, then listing them out. But i cant seem to get it right. Currently facing an issue where i get the error
"_TypeError (type 'Future' is not a subtype of type 'Widget')".
any ideas
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:multi_image_picker2/multi_image_picker2.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List<Asset> claims = <Asset>[];
final ImagePicker imgpicker = ImagePicker();
List<Asset>? imagefiles;
loadLimitedImages() async {
try {
var pickedfiles = await MultiImagePicker.pickImages(maxImages: 3);
if (pickedfiles != null) {
setState(() {
imagefiles = pickedfiles;
});
} else {
print("No image is selected.");
}
} catch (e) {
print("error while picking file.");
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(
'Testing Functions',
style: TextStyle(color: Colors.black),
),
centerTitle: true,
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.black),
),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(10),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
imagefiles != null ? loadLimitedImages() : Container(),
imagefiles?.length == 3
? Container()
: IconButton(
onPressed: () => loadLimitedImages(),
icon: Icon(Icons.camera_enhance),
iconSize: 100,
),
],
),
),
),
),
);
}
}
I can only have 3 max images and it must be stacked in a row, when the images are picked, it goes back to the screen. If 3 images are showing, the camera icon dissapears, if not it will be beside the picked images if less than 3.
You get this error because of the following line in your build() method.
imagefiles != null ? loadLimitedImages() : Container(),
Here you call loadLimitedImages() which will return a Future, which is not a Widget. That method does not return anything, so this won't work in the way you try to use it.
If I understand it correctly, you want to pick 3 images, that operation should be a result of an action, e.g. a user taps a button to pick images. That is where you can call your loadLimitedImages() method. You shouldn't call such a method inside the build() method, since it could run frequently. The build() method's purpose is to build the UI based on the current state.
If you need to pick images without user interaction you can initiate it in the initState() of your State.

Flutter RefreshIndicator method is being called but the context is not rebuild

When I delete the item from the list, then I go back and refresh the page, RefreshIndicator seems not working(The animation is working but not refreshing the page). I have searched a lot about this problem. I tried everything I found on the web but none of them worked for me. The problem is that I have the method of _refresh to call this method onRefresh but it didn't work. I debugged the code to see whether the refresh method is being called. As far as I see it seems it is being called because I see refresh method is called on the debug console. The ListView.builder also has the physics property and it's not shrunk. I saw one more solution that suggests adding items that fill the whole screen. I added as many items as I can but it didn't work. So any suggestions? I am suspecting from the FutureBuilder that is a parent of the ListView.builder, I tried to cover the FutureBuilder too but it didn't work either.
class _DraftsState extends State<Drafts> {
final SQFLiteHelper _helper = SQFLiteHelper.instance;
#override
void initState() {
print('init state is called');
super.initState();
_helper.getForms();
}
Future<void> _refresh() async {
print('refresh method is called');
await _helper.getForms();
}
//TODO: RefreshIndicator not working.
//TODO:When the list changed nothing is happening until the draft section is rebuilt
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<FormData>?>(
future: _helper.getForms(),
builder:
(BuildContext context, AsyncSnapshot<List<FormData>?> snapshot) {
if (snapshot.hasData && snapshot.data!.isEmpty) {
return const Center(
child: Text("Henüz kaydedilmiş taslak bulunmamaktadır."));
}
if (snapshot.hasError) {
return Center(
child: Text(
'Bir şeyler ters gitti.',
style: TEXT_STYLE,
));
}
if (snapshot.connectionState == ConnectionState.done) {
return RefreshIndicator(
backgroundColor: Colors.grey[700],
color: LIGHT_BUTTON_COLOR,
onRefresh: _refresh,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return CustomListTile(
formData: snapshot.data![index], index: index);
},
),
),
);
}
return const Center(
child: CircularProgressIndicator(),
);
}),
);
}
}
Future<void> _refresh() async {
print('refresh method is called');
setState(() {
await _helper.getForms();
});
}
use setState in your refresh function. coz you need to reload the build method. or I think you can use setState like this.
Future<void> _refresh() async {
print('refresh method is called');
await _helper.getForms();
setState(() { });
}

flutter: how to make a listview update without pressing a button?

so I'm currently working on an application that has a listview on the first screen (implemented on main.dart).
The listview fetches it's data from internet (async).
The problem is that, the listview does not get updated when the data is changed.
(I can implement this functionality simply by designing a 'reload' button and pressing it every time I want the new data. But that's not what I want right now).
In other words, how can I update the listview automatically?
EDIT1: ADDING SOME CODE
code might be messy; see the description at the end.
class RssFeed extends StatelessWidget {
String title;
String pubDate;
RssFeed(this.title, this.pubDate);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Align(
alignment: Alignment.topRight,
child: Text(title),
),
Text(pubDate)
],
),
);
}
}
class FeedsList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _FeedsListState();
}
}
class _FeedsListState extends State<FeedsList> {
List<Widget> list1 = new List<Widget>();
#override
void initState() {
super.initState();
ls();
}
Future ls() async {
list1.clear();
list.clear();
sites1.clear();
RSS_reader rss_reader = new RSS_reader();
for (var i in saver.list.items) {
sites1.add(
site(siteAdress: i.siteAdress, siteDescription: i.siteDescription));
}
var res = await rss_reader.Get_items(sites1);
for (var val in res) {
list.add(InkWell(
onTap: () => _launchURL(val.item.link),
child: Container(
height: 50,
color: Colors.amber[100],
child: Center(
child: new RssFeed(val.item.title, val.item.pubDate.toString()),
),
)));
}
print(list.length);
setState(() {
list1 = list;
});
}
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: list1.length,
itemBuilder: (BuildContext context, int i) {
return list1[i];
}));
}
}
DESCRIPTION:
As you can guess, this is a RSS reader.
So, I have a class RSSFeed; which makes one of the tiles of Listview.
then in the FeedsList class (stateful widget), I make the listview.
I have a class called RSS_reader and a method Get_items, which gets a bunch of sites as input and puts those sites' newest feeds in a list ('res' in the above code).
Then, I put the items in a list of 'Container's and then build the listview.
Then, in the main function, I create a container like below:
Container(
height: 500,
width: 580,
child: FeedsList(),
)
and there appears the problem; the FeedsList class does not get updated automatically. although if I put a button and navigate to FeedsList class through that button, the list is refreshed and OK.
Thanks for reading and help.
If you just want to fetch data once from your external source use a FutureBuilder, if you want to fetch data multiple times take a look to StreamBuilder. Both widgets will have the behavior you are looking for, with no refresh button.
Simple example of how to use a FutureBuilder:
Future<List<String>> _fetchData() {
return // fetch data from source
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _fetchData,
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if (snapshot.hasData && snapshot.data != null) {
// This widget will be built when data is fetched
const List<String> list = snapshot.data;
return ListView(
children: list.map(
(element) => ListTile(
title: Text(element),
),
).asList(),
);
} else {
// This widget will be built while you are waiting for your data to be fetched
return Container(
child: Center(
child: Text("Loading data..."),
),
);
}
},
);
}
You have to stream data and ListView will update automatically.
In the button that you say you can re call your ls() functions, your list should update on tap button
sample:
return Scaffold(
body: ListView.builder(
itemCount: list1.length,
itemBuilder: (BuildContext context, int i) {
return list1[i];
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.refresh),
onPressed: () => ls(),
),
);

Flutter pre-cache images

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()
),

How to implement a swipe to delete listview to remove data from firestore

Im very new to flutter and dart so this might be a basic question. However, what I would like to know is how to implement a swipe to delete method in a listview to delete data from firestore too.
I tried using the Dissmissible function but i dont understand how to display the list and I cant seem to understand how to remove the selected data as well.
This here is my dart code
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: new AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children:
<Widget>[
Text("INVENTORY",textAlign: TextAlign.center,) ,new IconButton(
icon: Icon(
Icons.home,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(widget: MyHomePage()),
);
})]),
),body: ListPage(),
);
}
}
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future getPosts() async{
var firestore = Firestore.instance;
QuerySnapshot gn = await
firestore.collection("Inventory").orderBy("Name",descending:
false).getDocuments();
return gn.documents;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPosts(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:(_, index){
return EachList(snapshot.data[index].data["Name"].toString(),
snapshot.data[index].data["Quantity"]);
});
}
}),
);
}
}
class EachList extends StatelessWidget{
final String details;
final String name;
EachList(this.name, this.details);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Card(
child:new Container(
padding: EdgeInsets.all(8.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new CircleAvatar(child: new Text(name[0].toUpperCase()),),
new Padding(padding: EdgeInsets.all(10.0)),
new Text(name, style: TextStyle(fontSize: 20.0),),
],
),
new Text(details, style: TextStyle(fontSize: 20.0))
],
),
),
);
}
}
You should use Dismissible widget. I used it for an inbox list retrieved from Firestore. Inside your EachList return something like this
return Dismissible(
direction: DismissDirection.startToEnd,
resizeDuration: Duration(milliseconds: 200),
key: ObjectKey(snapshot.documents.elementAt(index)),
onDismissed: (direction) {
// TODO: implement your delete function and check direction if needed
_deleteMessage(index);
},
background: Container(
padding: EdgeInsets.only(left: 28.0),
alignment: AlignmentDirectional.centerStart,
color: Colors.red,
child: Icon(Icons.delete_forever, color: Colors.white,),
),
// secondaryBackground: ...,
child: ...,
);
});
IMPORTANT: in order to remove the list item you'll need to remove the item from the snapshot list as well, not only from firestore:
_deleteMessage(index){
// TODO: here remove from Firestore, then update your local snapshot list
setState(() {
snapshot.documents.removeAt(index);
});
}
Here the doc: Implement Swipe to Dismiss
And here a video by Flutter team: Widget of the week - Dismissilbe
You can use the flutter_slidable package to achieve the same.
You can also check out my Cricket Team on Github in which I have did the same you want to achieve, using same package.
Example for how to use package are written here.
I'd like to add that when deleting a document from Firestore, no await is needed as the plugin automatically caches the changes and then syncs them up when there is a connection again.
For instance, I used to use this method
Future deleteWatchlistDocument(NotifierModel notifier) async {
final String uid = await _grabUID();
final String notifierID = notifier.documentID;
return await _returnState(users.document(uid).collection(watchlist).document(notifierID).delete());
}
in which I was waiting for the call to go through, however this prevented any other call to go through and only allowed one. Removing this await tag however solved my issue.
Now I can delete documents offline, and the changes will sync up with Firestore when a connection is regained. It's pretty cool to watch in the console.
I'd recommend watching this video about offline use with Firestore

Categories

Resources