I have built a chatbot using flutter and DialogueFlow I wanted to add a loading animation before the bot answers the question .
I have already made the loading animation but i am not able to show both, the text and the loading image.
class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.name, this.type});
final String text;
final String name;
final bool type;
List<Widget> botMessage(context) {
return <Widget>[
Container(
margin: const EdgeInsets.only(right: 16.0),
child: CircleAvatar(child: Image(
image:
NetworkImage('https://cdn3.iconfinder.com/data/icons/customer-support-
7/32/40_robot_bot_customer_help_support_automatic_reply-512.png'),
)
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(this.name,
style: TextStyle(fontWeight: FontWeight.bold)),
Container(
margin: const EdgeInsets.only(top: 5.0),
child: Text(text),
),
],
),
),
];
}
List<Widget> loader(context) {
return <Widget>[
Container(
margin: const EdgeInsets.only(right: 16.0),
child: CircleAvatar(child: Image(
image:
NetworkImage('https://cdn3.iconfinder.com/data/icons/customer-support-
7/32/40_robot_bot_customer_help_support_automatic_reply-512.png'),
)
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(this.name,
style: TextStyle(fontWeight: FontWeight.bold)),
Container(
margin: const EdgeInsets.only(top: 5.0),
child: Image(
image: AssetImage('assets/loader.gif'),
width: 25,
height: 25,
),
),
],
),
),
];
}
List<Widget> myMessage(context) {
return <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text(this.name, style: Theme.of(context).textTheme.subhead),
Container(
margin: const EdgeInsets.only(top: 5.0),
child: Text(text),
),
],
),
),
Container(
margin: const EdgeInsets.only(left: 16.0),
child: CircleAvatar(
child: Text(
this.name[0],
style: TextStyle(fontWeight: FontWeight.bold),
)),
),
];
}
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this.type ? myMessage(context) : botMessage(context),
),
);
}
}
I have just put loader instead of the botMessage in the last line of the code but how can i just show the loader for 5 seconds or so.
Mainly i want the loading animation like Google Assistant.
There are two ways to do what you want to do.
Create the object with a Boolean flag which is updated to true when data is received and using this flag as condition switch between text & loader widgets.
Use a futurebuilder widget & in future parameter use Future.delay with 5 seconds duration which is returning some value like 1 or true. Then inside the builder of futurebuilder check of snapshot has data. If snapshot don't have data, just show your loader else show your text.
See this video of futurebuilder to understand how it works.
https://youtu.be/ek8ZPdWj4Qo
Edited as #Maadhav asking for code,
Here is a little snippet which might help you.
FutureBuilder(
future: Future.delayed(Duration(seconds: 5), () => true),
builder: (context, snapshot){
if(!snapshot.hasData)
return CircularProgressIndicator();
return Text("Yay");
}
)
Here this FutureBuilder should be the container of your reply bubble in which you can return Text or any other widget according to your need.
For demo click here
The following can be used:
Future.delay(Duration(seconds: 5), () { });
Related
I am developing a quiz app which is calling questions and options from firebase. It is currently only showing texts in questions and options from the firebase documents. I want it to get the images and videos from firebase and display it on my flutter application. Is it possible? If yes, please guide me through it. Thank you!
This is the code where I am showing the questions from..
class QuestionPage extends StatelessWidget {
final Question question;
const QuestionPage({super.key, required this.question});
#override
Widget build(BuildContext context) {
var state = Provider.of<QuizState>(context);
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(question.text),
),
),
Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: question.options.map((opt) {
return Container(
height: 90,
margin: const EdgeInsets.only(bottom: 10),
color: Colors.black26,
child: InkWell(
onTap: () {
state.selected = opt;
_bottomSheet(context, opt, state);
},
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
state.selected == opt
? FontAwesomeIcons.circleCheck
: FontAwesomeIcons.circle,
size: 30),
Expanded(
child: Container(
margin: const EdgeInsets.only(left: 16),
child: Text(
opt.value,
style: Theme.of(context).textTheme.bodyText2,
),
),
)
],
),
),
),
);
}).toList(),
),
)
],
);
}
/// Bottom sheet shown when Question is answered
_bottomSheet(BuildContext context, Option opt, QuizState state) {
bool correct = opt.correct;
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 250,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(correct ? 'Good Job!' : 'Wrong'),
Text(
opt.detail,
style: const TextStyle(fontSize: 18, color: Colors.white54),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: correct ? Colors.green : Colors.red),
child: Text(
correct ? 'Onward!' : 'Try Again',
style: const TextStyle(
color: Colors.white,
letterSpacing: 1.5,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
if (correct) {
state.nextPage();
}
Navigator.pop(context);
},
),
],
),
);
},
);
}
}
From the looks of things it seems you manually stored the images and videos ( Not programmatically). This means you have to create a collection (if you do not have one already) and input the url for each file you have stored in the database).
For example, If a question document from your Question collection has an image, a question text, and the list of options, you have to store the string url of the image in that particular question document.
To get the image string url, click and copy the link:
Then add it into your collection:
After doing this, you access your documents in your flutter app and then open the image/video using:
Extended Image (for image).
Better Player. (for video).
i'm very new to Flutter.
I have a stateful widget. Every time i click a counter, the whole build function runs and the results are updated.
But is there a way to make it a stateless widget and update only the relevant part somehow so i could avoid rebuilding the screen?
I have a stateful widget. Every time i click a counter, the whole build function runs and the results are updated.
But is there a way to make it a stateless widget and update only the relevant part somehow so i could avoid rebuilding the screen?
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home:Home(),
));
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State {
int long = 0;
void increaseCounter(){
setState(() {
long++;
});
}
void decreaseCounter(){
if(long > 0) {
setState(() {
long--;
});
}
}
Text numberOfDeliveries(){
return Text(
'$long deliveries today',
style: TextStyle(
color:Colors.white,
fontSize: 24.0,
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:Text('Deliveries'),
backgroundColor: Colors.green,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Container(
color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child:Padding(
padding: const EdgeInsets.symmetric(vertical: 32.0, horizontal: 0.0),
child: Text(
'Long',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 32.0,
color: Colors.white,
),
),
),
),
Expanded(
child:
// is it possible to update this without running the whole build again?
numberOfDeliveries(),
),
Expanded(
child: Row(
children: [
Expanded(
child: Container(
padding: EdgeInsets.symmetric(vertical: 32.0, horizontal: 0.0),
color:Colors.blue[100],
child: FlatButton(
onPressed: (){
decreaseCounter();
},
child: Text('-'),
),
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(vertical: 32.0, horizontal: 0.0),
color:Colors.blue[500],
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(vertical: 32.0, horizontal: 0.0),
color:Colors.blue[900],
child: FlatButton(
onPressed: (){
increaseCounter();
},
child: Text('+'),
),
),
),
],
),
),
],
),
),
),
Expanded(
child: Container(
color: Colors.blue[800],
child: Row(
children: [
Text('Bottom'),
],
),
),
),
],
),
);
}
}
I am making layout for flutter application
What I want to do is
-----------------------
|[i] [text] |
| |
Icon should be the left (padding 5px)
And text should be the center of screen.
At first I should use the Column
However my layout is not the same proportion
It might be simple though , how can I make it??
Stack() is one of the many options that you can use. Something like this:
Stack(
children:<Widget>[
Padding(
padding: EdgeInsets.only(left: 5),
child: Icon(Icons.info),
),
Align(
alignment: Alignment.topCenter,
child: Text("I'm on the top and centered."),
),
],
),
One way you can do this is something like this..
Widget build(BuildContext context) {
return Column(
children: [
Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.info),
Text('text'),
Opacity(opacity: 0, child: Icon(Icons.info)),
],
),
),
Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.info),
Text('second text'),
Opacity(opacity: 0, child: Icon(Icons.info)),
],
),
),
],
);
}
Result:
I might be late, but I want this answer to be there, if some one would find it better for the development purposes in future time.
We can make a reusable widget, which we can use it inside the main widget. The widget will accept text, and icon to be passed when called
// IconData is the data type for the icons
Widget myWidget(String text, IconData icon) => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// this will be used a left-padding param
SizedBox(width: 20.0),
// using it here
Icon(icon, size: 28.0, color: Colors.greenAccent),
SizedBox(width: 5.0),
// this will take the remaining space, and then center the child
Expanded(child: Center(child: Text(text)))
]
);
To use in the main Widget, just do like this:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// calling our widget here
myWidget('FirstText', Icons.alarm),
SizedBox(height: 20.0), // this is used as a top margin
myWidget('SecondText', Icons.notifications)
]
)
If you wanna check out the sources which I used, please see:
Expanded class
SizedBox class
The result you will get is this:
This was an answer I gave a while ago for this same issue, but the other situation was vertical (a Column) instead of horizontal (a Row). Just swap the Row and Columns out for a Column and Rows in this example and you'll get the idea.
My code has grey added in order to show the difference between the two approaches.
A Stack will work, but it's overkill for this, this kind of problem is part of why we have Expandeds and Flexibles. The trick is to use three Flexibles (2 Expandeds and a Spacer). Put the Spacer on top. It and the bottom Expanded must have the same flex value in order to center the middle Expanded.
import 'package:flutter/material.dart';
class CenteringOneItemWithAnotherItemInTheColumn extends StatelessWidget {
const CenteringOneItemWithAnotherItemInTheColumn({
Key key,
}) : super(
key: key,
);
/// Adjust these values as needed
final int sameFlexValueTopAndBottom = 40; // 40%
final int middleFlexValue = 20; // 20%
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Column Alignment Question'),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Spacer(),
const Text(
'Cent',
style: TextStyle(
fontSize: 64,
),
),
const Spacer(),
const Text(
'Bot',
style: TextStyle(
fontSize: 64,
),
),
],
),
Column(
children: <Widget>[
/// The key is to have the Spacer and the bottom Expanded
/// use the same value for flex. That will cause the middle
/// child of the Column to be centered vertically.
Expanded(
flex: sameFlexValueTopAndBottom,
child: Container(
width: 100,
color: Colors.grey[300],
),
),
Expanded(
flex: middleFlexValue,
child: const Align(
alignment: Alignment.center,
child: Text(
'Cent',
style: TextStyle(
fontSize: 64,
),
),
),
),
Expanded(
flex: sameFlexValueTopAndBottom,
child: Container(
color: Colors.grey[300],
child: const Align(
alignment: Alignment.bottomCenter,
child: Text(
'Bot',
style: TextStyle(
fontSize: 64,
),
),
),
),
),
],
),
],
),
);
}
}
Is it possible to use PageView to reach the following work?
I'd like to use two containers as the main content for a page in PageView.
I've tried to adjust viewPortFraction but it doesn't work as I expected.
The following effect is used by Apple App store and Prime Video and many apps.
Thanks.
You could try to use an horizontal ListView with PageScrollPhysics() In the physics param.
Instead of using PageView, you could just use a ListView with
scrollDirection: Axis.horizontal
This way you could achieve the same result. You would need to somehow provide an explicit height to your ListView if I am not mistaken.
Below is a working sample.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Top Games",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
Container(
height: 200,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
GameCard(
"The Game 1",
"A puzzle game",
"https://lorempixel.com/200/200/abstract/",
),
GameCard(
"The Game 2",
"An even beter game",
"https://lorempixel.com/200/200/sports/2/",
),
GameCard(
"SportMania",
"Editors Choice",
"https://lorempixel.com/200/200/sports/3/",
),
GameCard(
"MonkeySports",
"Monkeys playing Sport",
"https://lorempixel.com/200/200/abstract",
),
],
),
)
],
),
);
}
}
class GameCard extends StatelessWidget {
final String gameTitle;
final String gameDescr;
final String imgUrl;
GameCard(
this.gameTitle,
this.gameDescr,
this.imgUrl,
);
#override
Widget build(BuildContext context) {
return Card(
elevation: 3,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
margin: EdgeInsets.all(5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
imgUrl,
width: 180,
height: 130,
fit: BoxFit.cover,
alignment: Alignment.center,
),
),
Container(
padding: EdgeInsets.all(4),
width: 180,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
gameTitle,
maxLines: 2,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
gameDescr,
maxLines: 2,
),
],
),
),
],
),
);
}
}
Hope this gets you started.
You can using viewportFraction params in PageController;
I believe I have successfully encoded an image to Base64 and stored as a JSON string in a JSON field.
In my app, I retrieve the field and then try to decode it; and, if it's there, I try to display it.
But I keep getting errors and no image appears. I have tried several things, but I keep getting assertion errors, either from Image_provider or Framework.
I have confirmed during debugging that I only have one record coming in with image data and it gets decoded and the assertions look correct.
Any thoughts or ideas?
#override
Widget build(BuildContext context) {
_key = new PageStorageKey('${widget.datediv.toString()}');
return new Column(
children: <Widget>[
new Container(
child: new Text(
mydate,
textAlign: TextAlign.left,
style: new TextStyle( color: Colors.grey, fontWeight: FontWeight.bold,),
),
alignment: Alignment.centerLeft,
padding: new EdgeInsets.only(left: 10.0),
),
new Container(
child: new Divider(
height: 5.0,
color: Colors.grey,
),
padding: new EdgeInsets.only(left: 10.0, right: 10.0),
),
/**/
new FutureBuilder(
future: _responseFuture,
builder:
(BuildContext context, AsyncSnapshot<http.Response> response) {
if (!response.hasData) {
return const Center(
child: const Text('Loading Messages...'),
);
} else if (response.data.statusCode != 200) {
return const Center(
child: const Text('Error loading data'),
);
} else {
List<dynamic> json = JSON.decode(response.data.body);
messagelist = [];
json.forEach((element) {
DateTime submitdate =
DateTime.parse(element['submitdate']).toLocal();
if (element['image'] != null) {
imgStr = element['image'];
Uint8List mybytes = BASE64.decode(imgStr);
}
_addImage() {
assert(imgStr != null);
new Container(
width: 150.0,
child: new Image.memory(mybytes),
);
}
_addNoImage() {
assert(imgStr == null);
new Text('');
}
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('${element['message']}'),
)
],
),
imgStr != null ? _addImage(): _addNoImage(),
],
),
),
);
});
return new Column(children: messagelist);
}
},
),
],
);
}
You may want to consider checking if the image bytes that you're using in Image.memory(bytes) is valid. Flutter's current null-safety feature should help catch this early on. If imgStr is nullable, you can add a null-check for verification before returning Image.memory(). If null, then return a placeholder Widget.
As for your other question on adjusting the dimensions of the widget displayed. Aside from BoxDecoration, you can also use ConstrainedBox with BoxConstraints
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: _maxWidth,
minWidth: _minWidth,
),
child: Image(...)
)