I have a custom popup built, and an image is supposed to change whenever one of my variables is changed. When I call the setState method, the content in my showDialog doesn't change.
What am I doing wrong, or is there a better approach? Trying to change the state so the image can be changed in the showDialog.
Here's my code:
class LocationManagerPage extends StatefulWidget {
const LocationManagerPage({Key? key}) : super(key: key);
#override
State<LocationManagerPage> createState() => _LocationManagerPageState();
}
class _LocationManagerPageState extends State<LocationManagerPage> {
String downloadURL = "";
Future _uploadFile(String path) async {
// Logic that gets the download url to an image...
// When the download url is found, calling setState method
setState(() {
downloadURL = fileUrl;
});
}
showLocationPopup() {
return showDialog(
context: context,
builder: (context) {
return Center(
child: Material(
child: Container(
width: 427,
height: 676,
decoration: BoxDecoration(...),
child: SingleChildScrollView(
child: Column(
children: [
// Popup UI Widgets,
Center(
child: Container(
height: 150,
width: 150,
decoration: BoxDecoration(),
child: ClipRRect(
child: Image.network(
image,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(20),
),
),
),
SizedBox(
height: 15,
),
Center(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () async {
String? imageUrl = await urlFromWebImage();
print(imageUrl);
setState(() {
downloadURL = imageUrl!;
});
},
child: Button(
name: imageName,
),
),
),
),
// The rest of the popup UI
],
),
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
.... // Not Important
);
}
}
To update dialog ui you need to use StatefulBuilder widget on showDialog's builder and use StatefulBuilder's setState.
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
you can use the following approach
first initialize download url like this
ValueNotifier<String> downloadUrl = ValueNotifier("");
ValueListenableBuilder(
valueListenable: downloadUrl,
builder: (context, value, Widget? c) {
return Container(
height: 150,
width: 150,
decoration: BoxDecoration(),
child: ClipRRect(
child: Image.network(
downloadUrl.value, // here put download url it will auto update
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(20),
));
});
and without using setstate put value in download url it will auto update ui
downloadUrl.value = "" //your image url
or you can use StateFulBuilder
setstate rebuild your whole widget but upper approach only build image widget
Related
Im trying to get url from cloud firebase and then use it in Image.network but it doesn't work..
When i hardcode the url inside Image.network it works.. the variable did get the url as the data.
I get an error from image.dart - ImageStreamListener throw error.
this is my code:
class _MemoryCardState extends State<MemoryCard> {
Map<String, dynamic> photos = {};
Future getPhoto() async {
photos.clear();
var db = FirebaseFirestore.instance.collection('photos');
await db.doc(widget.id).get().then((DocumentSnapshot snapshot) {
photos = snapshot.data() as Map<String, dynamic>;
});
}
#override
Widget build(BuildContext context) {
var deviceWidth = MediaQuery.of(context).size.width;
var deviceHeight = MediaQuery.of(context).size.height;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
elevation: 10,
color: Theme.of(context).colorScheme.surfaceVariant,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: SizedBox(
width: deviceWidth * 0.8,
height: deviceWidth * 0.35,
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const Memory()));
},
child: Stack(
children: [
FutureBuilder(
future: getPhoto(),
builder: (context, snapshot) {
String url = photos['url'].toString();
return Hero(
tag: 'image',
child: Image.network(
url,
fit: BoxFit.cover,
width: deviceWidth * 0.8,
color: Colors.white.withOpacity(0.5),
colorBlendMode: BlendMode.modulate,
));
}),
],
),
),
),
),
SizedBox(height: deviceHeight * 0.2),
],
);
}
}
The error you are encountering is likely caused by the fact that the url variable is not yet available when the Image.network widget is first rendered. The FutureBuilder widget is used to handle this issue, but it is not being used correctly in your code.
A FutureBuilder widget should be used to rebuild the widget tree when the future completes.
You should move the FutureBuilder outside of the InkWell and Stack widgets.
FutureBuilder(
future: getPhoto(),
builder: (context, snapshot) {
if (snapshot.hasData) {
String url = photos['url'].toString();
return InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const Memory()));
},
child: Stack(
children: [
Hero(
tag: 'image',
child: Image.network(
url,
fit: BoxFit.cover,
width: deviceWidth * 0.8,
color: Colors.white.withOpacity(0.5),
colorBlendMode: BlendMode.modulate,
)),
],
),
);
} else {
return CircularProgressIndicator();
}
},
);
Also, it will be a better practice to check if the snapshot hasData before trying to access the url.
Also, you can use await keyword to wait for the data retrieval to complete, before using the url value in the Image.network.
give the url which you found from firebase.
If you used URI as DataType in firebase then it is the problem.
I'm trying to load image from network and display it fully along with button on top of the image. To achieve this I looked up on various solution and found that this can be done using Stack widget. My implementation is as below
class DisplayImage extends StatefulWidget {
final String text;
DisplayImage({required this.text}) ;
#override
State<DisplayImage> createState() => _DisplayImageState();
}
class _DisplayImageState extends State<DisplayImage> {
#override
initState() {
// TODO: implement initState
_asyncMethod();
super.initState();
}
_asyncMethod() async {
Image.network(widget.text);
setState(() {
dataLoaded = true;
});
}
bool dataLoaded = false;
#override
Widget build(BuildContext context) {
if (dataLoaded){
return Scaffold(
backgroundColor: Colors.lightBlueAccent,
appBar: AppBar(title: Text("Selfie BGchanger"),centerTitle: true,
),
body: Center(child: Stack(
children: [Image.network(
widget.text,
fit: BoxFit.fill,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
const SizedBox(height: 50,),
Align(
alignment: Alignment(0, .2),
child: ElevatedButton(child: const Text('Save',style: TextStyle(fontWeight: FontWeight.normal)),style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
primary: Colors.black,
// padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20),
textStyle: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold)),
onPressed: () async{
String url = widget.text;
var imageId = await ImageDownloader.downloadImage(url);
if(imageId == null)
{return;}
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Saved to gallery!')));
Fluttertoast.showToast(msg: "Image saved to Gallery");
},
),
),
],),
),
);
} else {
return CircularProgressIndicator(backgroundColor: Colors.cyan,strokeWidth: 5,);
}
}
}
with this I get image is as below
save button is on top but what I'm trying to get is as below
Expected:
full sized image with save button on bottom center
I tried using boxfit.cover with height and width as infinit as below
fit: BoxFit.cover,
// height: double.infinity,
// width: double.infinity,
I got display error
How can I fix this to get expected image ? any help or suggestion on this will be highly appreciated
update:
based on answer suggestion I modified code as above and get output as below
Wrap your ElevatedButton widget with Positioned/Align widget.
Align(
alignment: Alignment(0, .2), //adjust based on your need
child: ElevatedButton(
Also you find more about Stack , Align widget.
body: Stack(
children: <Widget>[
Positioned.fill(
child: Image.network(
"",
fit: BoxFit.cover,
)),
Align(
alignment: Alignment(0, .2), // change .2 based on your need
child: ElevatedButton(
onPressed: () async {
await showDatePicker(
context: context,
initialEntryMode: DatePickerEntryMode.inputOnly,
initialDate: DateTime.now(),
firstDate: DateTime.now().subtract(Duration(days: 33)),
lastDate: DateTime.now().add(Duration(days: 33)),
);
},
child: Text("Dialog"),
),
),
],
),
If I am having an image in my HTML, I am not able to open that image to full screen, if tapped.
Below is the built in function available in flutter_html if image is tapped.
onImageTap: (url, context, attributes, element) => {
// code here
}
Is there any way we can achieve this?
I have tried below solution but it didn't worked
onImageTap: (url, context, attributes, element) => {
Scaffold(
body: GestureDetector(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Hero(
tag: 'imageHero',
child: Image.network(image),
),
),
onTap: () {
Navigator.pop(context);
},
),
)
}
You have to push it as a new page using Navigator.push
onImageTap: (url, context, attributes, element) => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => FullScreenImageViewer(url)),
);
}
Here is your stateless widget:
class FullScreenImageViewer extends StatelessWidget {
const FullScreenImageViewer(this.url,{Key? key}) : super(key: key);
final String url;
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Hero(
tag: 'imageHero',
child: Image.network(url),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
It seems to need Navigator.push() or showDialog.
onImageTap is not rebuild your screen.
I want to open a dialog in a parent statefull widget and use a callback to trigger that function from child class statefull widget, but the function called in parent does not get executed from child, the function does get executed but the showDialog seems not to be used, I even tried to move the function responsible to open to dialog to the child class but showDialog does not work either.
Here the code:
shareDilog function in parent class:
shareDialog(screenWidth, BuildContext cont) {
return showDialog(
context: cont,
builder: (BuildContext cont) {
print('inside inside');
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
elevation: 0,
child: StatefulBuilder(
builder: (BuildContext cont, StateSetter setState) {
return Container(
height: 680,
width: 650,
decoration: BoxDecoration(
color: Color(0xFF282828),
),
child: ShareDialog(),
);
}),
);
},
barrierDismissible: false
);
}
Where I pass this shareDialog function in parent to child:
Record(
visibleColumns: visibleColumns,
recordFullNameFieldName: recordFullNameFieldName,
oneRecord: listRecordsFilter[index],
screenWidth: screenWidth,
openShareDialog: shareDialog, // <-------
dashboardContext: context, // <--------
)
Where the call gets executed in child:
final Function(double, BuildContext)? openShareDialog; // the constructor parameter
PopupMenuItem(
onTap: () {
try {
widget.openShareDialog!(widget.screenWidth!, widget.dashboardContext!); // <---here the call back
// _showMyDialog(); // This is the test when I moved the function to the child
} catch(e) {
print(e);
}
},
child: ListTile(
leading: Icon(Icons.groups_sharp),
title: Text('Share'),
),
),
This can be achieved in many ways, here is my way:
Create the shareDialog() function in the parent widget. please try to assign different names for all the contexts. this may confuse flutter.
here is a mini version of your code for the same:
shareDialog(screenWidth, BuildContext cont) {
return showDialog(
context: cont,
builder: (ctx) {
print('inside inside');
return Dialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
elevation: 0,
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xFF282828),
),
child: Text(
"Hello World!",
style: TextStyles.title2.colour(Colors.white),
),
),
);
},
);}
Next pass the function and other parameters to the child widget.
Record(showDialog: shareDialog)
and finally, use the passed function in the child widget. here I have created a dummy Record widget that contains a listTile which on tapping executes the shareDialog function:
class Record extends StatelessWidget {
final Function showDialog;
Record({this.showDialog});
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () => showDialog(SizeConfig.screenWidth, context),
title: Text("Tile"),
leading: CircleAvatar(
backgroundColor: Colors.amber,
child: Icon(Icons.umbrella, color: Colors.white),
),
subtitle: Text("Show Share dialog"),
);
}
}
So I am implementing a 'settings' view in my Flutter app. The idea is all settings will appear in a ListView, and when the user will click on a ListTile, a showModalBottomSheet will pop where the user will be able to manipulate the setting. The only problem I am having is I am unable to migrate the showModalBottomSheet to a separate class as I cannot make the new function (outside the class) return the manipulated setting variable. This has lead to a messy code, all in a single class.
class Page extends StatefulWidget {
Page({Key key}) : super(key: key);
#override
_Page createState() => _Page();
}
class _Page extends State<Page> {
var value;
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
title: Text("Age"),
trailing: Text(value),
onTap: () {
setState(() {
value = _valueSelector(); // This doesn't work, but to give an idea what I want
});
},
),
],
);
}
}
int _valueSelector(context) { // Doesn't return
var age = 0;
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Wrap(
children: [
Column(
children: <Widget>[
Slider(
value: age.toDouble(),
min: 0,
max: 18,
divisions: 18,
onChanged: (value) {
setState(() {
age = value.toInt();
});
},
),
],
),
],
);
});
},
).whenComplete(() {
return age; // Not sure if return is supposed to be here
});
}
How can I implement showModalBottomSheet in a separate class and just make it return the variable representing the setting chosen by the user?
You can try the below code,
First, create a class custom_bottom_sheet.dart and add the below code. You can use it everywhere in the project. And also use this library modal_bottom_sheet: ^0.2.0+1 to get the showMaterialModalBottomSheet.
customBottomSheet(BuildContext context, {#required Widget widget}) async {
return await showMaterialModalBottomSheet(
context: context,
backgroundColor: AppColors.transparent_100,
barrierColor: AppColors.black_75,
isDismissible: false,
enableDrag: true,
builder: (_, ScrollController scrollController) {
return widget;
},
);
}
Sample example code:
Create another class called bottom_sheet_example.dart and add the below code.
class BottomSheetExample {
static Future getSheet(BuildContext _context,
{ValueChanged<bool> onChanged}) async {
await customBottomSheet(
_context,
widget: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 40.0, right: 40.0),
height: 170.0,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(27.0),
topRight: Radius.circular(27.0))),
child: Container(
padding: EdgeInsets.only(top: 32),
child: Column(
children: [
Text("Were you at Queen Victoria Building?"),
SizedBox(height: 48),
Row(
children: [
Expanded(
child: RaisedButton(
child: Text("No"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(false);
},
),
),
SizedBox(width: 18),
Expanded(
child: RaisedButton(
child: Text("Yes"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(true);
},
),
),
],
),
SizedBox(height: 24),
],
),
),
)),
);
}
}
Button click to show the bottom sheet
#override
Widget build(BuildContext context) {
return Scaffold(
body: yourBodyWidget(),
bottomNavigationBar: Container(
height: 40,
width: double.infinity,
child: FlatButton(
onPressed: () {
/// call BottomSheetExample class
BottomSheetExample.getSheet(
context,
onChanged: (bool result) async {
///
/// add your code
},
);
},
child: Text("show bottom sheet")),
),
);
}
In onChanged callback you can return your value(obj/String/num/bool/list).
Thank you!