I have the following code which displays network images in a carousel. When I start my app, I see a blank white screen in my carousel. I would like to show a placeholder image instead. I tried to put the placeholder image under the if clause but the if clause displays the placeholder only when I navigate to another screen and come back to the carousel screen. I want the placeholder to be displayed when I start my app. How can I achieve this ?
return Container(
child: FutureBuilder(
future: getCarouselWidget(),
builder: (context, AsyncSnapshot snapshot) {
List<NetworkImage> list = new List<NetworkImage>();
if (snapshot.connectionState == ConnectionState.waiting || snapshot.connectionState == ConnectionState.active
) {
debugPrint("connection state is " + snapshot.connectionState.toString() );
return new FadeInImage(
height: 200.0, // Change it to your need
width: 300.0, // Change it to your need
fit: BoxFit.cover,
placeholder: new AssetImage("assets/placeholder.jpg"),
image: new AssetImage("assets/placeholder.jpg"),
);
} else if(snapshot.connectionState == ConnectionState.done) {
debugPrint("connection state is inside else" + snapshot.connectionState.toString() );
if (snapshot.hasError) {
return new Text("fetch error");
} else {
for (int i = 0; i < snapshot.data.length; i++) {
//debugPrint("Index is " + idx.toString());
list.add(NetworkImage(snapshot.data[i].data["image"]));
//idx++;
}
return new Container(
height: 250.0,
child: InkWell(
child: new Carousel(
boxFit: BoxFit.cover,
images: list,
onImageTap: (imageIndex) {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) => new CustomClass(
name:
snapshot.data[imageIndex].data["title"],
pic: snapshot
.data[imageIndex].data["image"]),
),
);
},
autoplay: false,
dotSize: 4.0,
indicatorBgPadding: 4.0,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 1000),
)));
}
}
}),
);
you can use cached network image library which provides delay, placeholder and fade animations, an example :
child: new CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: new Image.asset('assets/placeholder.jpg'),
errorWidget: new Icon(Icons.error),
fit: BoxFit.fill,
fadeInCurve: Curves.easeIn ,
fadeInDuration: Duration(seconds: 2),
fadeOutCurve: Curves.easeOut,
fadeOutDuration: Duration(seconds: 2),
)
Related
//Inside home.dart file
Stream<List<Usern>> getAllUsers() async* {
var reference = firestore.collection("users").snapshots();
Usern user;
List<Usern> users = [];
reference.forEach((element) {
element.docs.forEach((doc) {
Map data = doc.data();
user = Usern.fromJSON(data);
users.add(user);
});
});
yield users;
}
//Stream Builder code
StreamBuilder<List<Usern>>(
stream: getAllUsers(),
builder: (context, snapshot) {
List<Widget> children = [];
if (snapshot.hasData) {
final List data = snapshot.data;
data.forEach((element) {
children.add(
GestureDetector(
onTap: () {
print("my id is:$myid");
createChatRoom(
myid,
element.id,
);
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: Column(
children: [
Expanded(
child: CircleAvatar(
radius: 20.0,
backgroundImage: NetworkImage(
element.profilePicture),
),
),
SizedBox(
height: 20,
),
Text(
element.displayName.split(" ")[0],
),
],
)),
),
);
print(element.displayName);
});
return Row(
children: children,
);
}
return Text("Loading");
}),
Whenever I use app from start the list of users doesn't appear in
the row widget .As
soon as I press hot restart when I am in home widget the list of users in row
appears...Is there something wrong with my code that streambuilder is not working?
Does streambuilder works only when you add data to the stream or does it also check
previous datas and builds? I mean to say when user logs in the app and does
nothing, I want all the users to be shown in the row widget just like in messenger
app.....does streambuilder only builds itself when it finds that the data has been
added to the stream?
new to flutter. I know how to set state the alert dialog, but with the need of tap to function like ()=> _createPlayer, It does not want to rebuild the alert dialog.
I wonder how to set state on alert dialog when you need to tap them.
File _image;
GestureDetector(
onTap: () => _createPlayer(),
After tap, it will display an alert dialog like this:
_createPlayer() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0))),
content: Container(
height: 400,
width: 300,
child: Column(
children: <Widget>[
Text('Create Player', style: Theme
.of(context)
.textTheme
.body1),
GestureDetector(
onTap: _getImageCamera,
child: CircleAvatar(
radius: 100,
backgroundColor: Colors.white,
backgroundImage: _image != null ? FileImage(_image) : AssetImage('assets/images/undercover.png'),
),
),
],
),
),
);
});
}
_getImageCamera() async{
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image;
});
}
I want to set state/change the image in alert dialog when selected. Any idea?
You can use StatefulBuilder for change inside dialog
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
// add StatefulBuilder to return value
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title of Dialog"),
content: Text(contentText),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
FlatButton(
onPressed: () {
setState(() {
contentText = "Changed Content of Dialog";
});
},
child: Text("Change"),
),
],
);
},
);
},
);
Create a separate Stateful Widget CustomDialog for the AlertDialog and move the _getImageCamera function _image variable inside it like this
_createPlayer() {
return CustomDialog();
}
class CustomDialog extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return CustomDialogState();
}
}
class CustomDialogState extends State<CustomDialog> {
ImageProvider _image;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0))),
content: Container(
height: 400,
width: 300,
child: Column(
children: <Widget>[
Text('Create Player', style: Theme
.of(context)
.textTheme
.body1),
GestureDetector(
onTap: _getImageCamera,
child: CircleAvatar(
radius: 100,
backgroundColor: Colors.white,
backgroundImage: _image != null ? FileImage(_image) : AssetImage('assets/images/undercover.png'),
),
),
],
),
),
);
});
}
_getImageCamera() async{
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image;
});
}
}
In order to see UI changes on showDialog, you have to create a new StatefulWidget and then work with dialog in that class. Here is the example/sample code
The most stupidest and quickest fix is:
Navigator.of(context).pop();
Then call the showDialog() again.
There will be a micro delay but works.
Edit:
Home Page - I'm fetching a list of strings from my firebase collection. I then want to make a call to firestore storage and get the downloadable image link and store it in a list that I will pass to Page 2. The code below is how i'm getting the downloadable links.
Future<List<String>> test(List images) async{
List<String> listOfImages = List<String>();
for(int i = 0; i < images.length; i++){
final ref = FirebaseStorage.instance.ref().child(images[i]);
var url = await ref.getDownloadURL();
listOfImages.add(url);
}
return listOfImages;
}
I'm then passing the list of Images in the following manner
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CarDetailScreen(
carImages: test( car['images'])
)),
);
Page 2 - I'm receiving a Future> that I would like to convert to a List
List<T> map<T>(List list, Function handler) {
List<T> result = [];
for (var i = 0; i < list.length; i++) {
result.add(handler(i, list[i]));
}
return result;
}
new CarouselSlider(
items: ***["This is where I need a List"]***.map((url) {
return new Container(
margin: new EdgeInsets.all(5.0),
child:
new GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ViewCarImages(
carImages: _getImages(snapshot))),
);
},
child:
new ClipRRect(
borderRadius: new BorderRadius.all(new Radius.circular(5.0)),
child:
new Image.network(
url,
fit: BoxFit.cover,
width: 1000.0,
)
)
)
);
}).toList(),
viewportFraction: 0.9,
aspectRatio: 2.0,
autoPlay: false,
)
You can just return a List of Widget, like this:
Future<List<Widget>> test(List images) async{
List<Widget> listOfImages = List<Widget>();
for(int i = 0; i < images.length; i++){
final ref = FirebaseStorage.instance.ref().child(images[i]);
var url = await ref.getDownloadURL();
listOfImages.add(Image.network(url));
}
return listOfImages;
}
Below code will solve your problem. In carouselSlide class(check items variable) written below and you need to follow below procedure to work the Future object in carousel
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
backgroundColor: Colors.white,
title: Text("some text",
style: TextStyle(
fontWeight: FontWeight.normal,
color: Colors.black,
fontSize: 16.0)),
centerTitle: false,
actions: <Widget>[
IconButton(
tooltip: 'Search something',
icon: Icon(
Icons.search,
color: SharedColor().sapphireDarkBlue,
),
onPressed: () async {
var selected = await showSearch<int>(
context: context, delegate: _delegate);
if (selected != null && selected != _lastIntegerSelected) {
setState(() {
//pass the data from searchfield query to here
});
}
},
),
],
automaticallyImplyLeading: false,
),
body: _carouselBuild(),
);
}
FutureBuilder<caraouselImage> _carouselBuild() {
return FutureBuilder<caraouselImage>(
future: ImageRestApiManager().caraouselImage(myEmail),
builder: (context, AsyncSnapshot<caraouselImage> snapshot) {
if (snapshot.hasData) {
return CarouselSlider(
items: snapshot.data.collection.map((i) {
return Builder(builder: (BuildContext context) {
return Container(
margin: EdgeInsets.all(5.0),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
child: Stack(children: <Widget>[
Image.network(i.imageurl,
fit: BoxFit.cover, width: 1000.0),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromARGB(200, 0, 0, 0),
Color.fromARGB(0, 0, 0, 0)
],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
),
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 20.0),
child: Text(
_currentCookieSelected.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
),
]),
),
);
});
}).toList(),
autoPlay: false,
enlargeCenterPage: true,
viewportFraction: 0.9,
aspectRatio: 2.0,
onPageChanged: (index) {
setState(() {
_currentImageSelected = snapshot.data.collection[index].id;
});
});
} else {
return CircularProgressIndicator();
}
});
}
}
I solved using below youtube tutorial
https://www.youtube.com/watch?v=xBLtPDHvT44
A FutureBuilder should get you wht you're looking for. Here, I've wrapped your CarouselSlider in a FutureBuilder, and have the future property set to your carImages Future. When your list of Strings is ready, the FutureBuilder will create the CarouselSlider via its builder method, and all of the list items will be present then:
return FutureBuilder<List<String>>(
future: carImages,
initialData: [],
builder: (context, snapshot) {
return new CarouselSlider(
viewportFraction: 0.9,
aspectRatio: 2.0,
autoPlay: false,
items: snapshot.data.map((url) {
return new Container(
margin: new EdgeInsets.all(5.0),
child:
new GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ViewCarImages(
carImages: _getImages(snapshot))),
);
},
child: new ClipRRect(
borderRadius: new BorderRadius.all(new Radius.circular(5.0)),
child: new Image.network(
url,
fit: BoxFit.cover,
width: 1000.0,
)
)
)
);
}).toList(),
);
}
);
using the flutter plugin image_picker 0.4.4 I'm getting displaying an image from camera or gallery with the following
Widget _previewImageBkg() {
return FutureBuilder<File>(
future: _imageFileBkg,
builder: (BuildContext context, AsyncSnapshot<File> snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.data != null) {
print('_previewImage.........check callback for this image. .>>>>>');
final File file = snapshot.data;
myBgURL = uploadFile('user#image.com_B.jpg', file);
return Image.file(snapshot.data);
} else if (snapshot.error != null) {
return const Text(
'Error picking image.',
textAlign: TextAlign.center,
);
} else {
return const Text(
'You have not yet picked an image.',
textAlign: TextAlign.center,
);
}
}
)
the image does display fine if you put in a Container
return Scaffold(
appBar: AppBar(
title: Text('Set Profile Images'),
),
body: Center(
child: new Container(
width: screen.width,
height: 250.0,
child: _previewImageBkg()),
),
However I would like to display it in a BoxDecoration(image: ...)
child: new Container(
width: screenSize.width,
height: 275.0,
decoration: new BoxDecoration(
image: new DecorationImage(
image: (_previewImageBkg()== null ? new ExactAssetImage('images/nav_header_bg.png') : _previewImageBkg()),
fit: BoxFit.cover,
),
),
),
/flutter (29415): type 'FutureBuilder<File>' is not a subtype of type 'ImageProvider<dynamic>'
How do I cast this File to Image ?
The greater objective to get the - fit: BoxFit.cover - property of the BoxDecoration.
Use FileImage like this, to build your decorated Container - the associated Future must return a File:
#override
Widget build(BuildContext context) {
return new FutureBuilder<File>(
future: _imageFileBkg,
builder: (context, snapshot) {
if (!snapshot.hasData) return Container(); // or some other placeholder
return new Container(
width: screenSize.width,
height: 275.0,
decoration: new BoxDecoration(
image: new DecorationImage(
image: new FileImage(snapshot.data),
fit: BoxFit.cover,
)),
);
},
);
}
UPDATE
This is the StreamBuilder code:
I am trying currently to update with a timer that runs a Stream.fromFuture which updates the data, but with the flicker and scroll weirdness.
new StreamBuilder(
initialData: myInitialData,
stream: msgstream,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Waiting to start');
case ConnectionState.waiting:
return new Text('');
default:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
myInitialData = snapshot.data;
return new RefreshIndicator(
child: new ListView.builder(
itemBuilder: (context, index) {
Stream<List<Map>> msgstream2;
Future<List<Map>> _responseDate = ChatDB.instance.getMessagesByDate(snapshot.data[index]['msgkey'], snapshot.data[index]['msgdate']);
msgstream2 = new Stream.fromFuture(_responseDate);
return new StreamBuilder(
initialData: myInitialData2,
stream: msgstream2,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Waiting to start');
case ConnectionState.waiting:
return new Text('');
default:
List messList;
var mybytes;
File myimageview;
Image newimageview;
String imgStr;
String vidStr;
String vidimgstr;
myInitialData2 = snapshot.data;
List<dynamic> json = snapshot.data;
List messagelist = [];
json.forEach((element) {
DateTime submitdate =
DateTime.parse(element['submitdate']).toLocal();
String myvideo = (element['chatvideo']);
String myimage = element['chatimage'];
String myvideoimage = element['chatvideoimage'];
File imgfile;
File vidfile;
File vidimgfile;
bool vidInit = false;
Future<Null> _launched;
String localAssetPath;
String localVideoPath;
String mymessage = element['message'].replaceAll("[\u2018\u2019]", "'");
//print('MYDATE: '+submitdate.toString());
_checkFile(File file) async {
var checkfile = await file.exists();
print('VIDEXISTS: '+checkfile.toString());
}
Future<Null> _launchVideo(String url, bool isLocal) async {
if (await canLaunchVideo(url, isLocal)) {
await launchVideo(url, isLocal);
} else {
throw 'Could not launch $url';
}
}
void _launchLocal() =>
setState(() => _launched = _launchVideo(localVideoPath, true)
);
Widget _showVideo() {
return new Flexible(
child: new Card(
child: new Column(
children: <Widget>[
new ListTile(subtitle: new Text('Video'), title: new Text(element['referralname']),),
new GestureDetector(
onTap: _launchLocal,
child: new Image.file(
vidimgfile,
width: 150.0,
),
),
],
),
)
);
}
if (myimage != "") {
imgStr = element['chatimage'];
imgfile = new File(imgStr);
}
if (myvideo != "") {
vidStr = element['chatvideo'];
vidimgstr = element['chatvideoimage'];
vidimgfile = new File(vidimgstr);
localVideoPath = '$vidStr';
}
_showLgPic() {
Route route = new MaterialPageRoute(
settings: new RouteSettings(name: "/ShowPic"),
builder: (BuildContext context) => new ShowPic(
image: imgfile,
),
);
Navigator.of(context).push(route);
}
Widget _showGraphic() {
Widget mywidget;
if (myimage != "") {
mywidget = new GestureDetector(
child: new Image.file(
imgfile,
width: 300.0,
),
onTap: _showLgPic,
);
} else if (myvideo != "") {
mywidget = _showVideo();
} else {
mywidget = new Container();
}
return mywidget;
}
messagelist.add(
new Container(
//width: 300.0,
padding: new EdgeInsets.all(10.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Container(
padding: new EdgeInsets.only(bottom: 5.0),
child: new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new CircleAvatar(
child: new Text(
element['sendname'][0],
style: new TextStyle(fontSize: 15.0),
),
radius: 12.0,
),
new Text(' '),
new Text(
element['sendname'],
style: new TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold),
),
new Text(' '),
new Text(
new DateFormat.Hm().format(submitdate),
style: new TextStyle(
color: Colors.grey, fontSize: 12.0),
),
],
),
),
new Row(
children: <Widget>[
new Text(' '),
new Flexible(
child: new Text(mymessage),
)
],
),
new Container(
width: 150.0,
child: new Row(
children: <Widget>[
new Text(' '),
_showGraphic()
],
)),
],
),
),
);
});
return new Column(children: messagelist);
}
}
);
/*return new MyChatWidget(
datediv: snapshot.data[index]['msgdate'],
msgkey: snapshot.data[index]['msgkey'],
);*/
},
//itemBuilder: _itemBuilder,
controller: _scrollController,
reverse: true,
itemCount: snapshot.data.length,
),
onRefresh: _onRefresh
);
}
}
}),
I started with a Future> from a local sqlite DB. I take that future and use the data to to get another Future> from the DB. I use Listview.builder to build the widget, etc... All works great, but need to refresh the data in realtime as messages come in and get updated in the DB. I converted the furtures to streams and use a timer to go get new data, but of course my screen flickers and eventhough the data refreshes its ugly.
So I am trying to figure out a better way to get the data to update without the flicker and not affect the user if they are scrolling on the page looking at messages.
I currently do the 2 futures, because one is used to build Date Dividers between messages for each Date. I have been looking at have a StreamController and subscribing to it, but not clear how to update the data in the controller as the the data needs to be full when they come to the page and then added to as the new messages sync.
So I have been looking at something like this that I found:
class Server {
StreamController<List<Map>> _controller = new StreamController.broadcast();
void addMessage(int message) {
var newmsg = await database.rawQuery('select c.*, date(submitdate, "localtime") as msgtime from v_groupchats g join chats c on c.id = g.id where (oid = $oid or prid = $prid) and c.msgkey not in (select msgkey from chatArchive) order by submitdate desc');
_controller.add(message);
}
Stream get messages => _controller.stream;
}
This is not complete, just hoping it helps someone with some ideas for me.
Thanks in advance for any help.
This flickering is most likely the result of your:
case ConnectionState.waiting:
return new Text('');
Because every time you fetch data, your stream will enter a brief moment of ConnectionState.waiting before it is ConnectionState.done. And what you did was tell the UI to display essentially nothing Text('') every time while it fetches data. Even if it only takes like 50ms, it's noticeable to the human eye...
So if you don't care while your stream is fetching data, then remove that check. Otherwise, you could change your layout into a Stack and overlay a loading animation somewhere, or just something to indicate that it's currently fetching data.
(note that I was able to see this flickering when I tried to test your setup with a simulated Future.delayed(const Duration(milliseconds: 50), () => data), and we can perceive quicker still)