State not updating in dialog box when using providers - flutter

I am trying to open a dialog box to let users add 2 things:
1- Video thumbnail(Image file)
Video URL(String)
The user taps on an Inkwell which opens the dialog box, then the user clicks the inkwell in the dialog box to pick a video thumbnail i.e image file. The image file gets set, but it doesn't update the state of the inkwell which needs to show the picked file. The state is updated after hot reload. I am using the provider to set the image file and using its instance to check if the file is picked or not.
Change Notifier ->
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class GetThumbnailImage extends ChangeNotifier {
PickedFile? image;
Future setImage(var file) async {
image = file;
notifyListeners();
}
}
Widgets using the change notifier ->
InkWell(
onTap: () {
getVideoImage().then((image) {
print(image);
if (image != null) {
_imageInstance.setImage(image);
}
});
},
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.25,
child: Center(
child: _imageInstance.image == null
? SizedBox(
width: 200,
height: 200,
child: FadeInImage(
image: NetworkImage(
apiBase + productId.toString() + apiLaterPart),
placeholder:
AssetImage("assets/images/Otherimages/addvideo.jpg"),
imageErrorBuilder: (context, error, stackTrace) {
return Image.asset(
'assets/images/Otherimages/addvideo.jpg',
fit: BoxFit.fitWidth);
},
fit: BoxFit.fitWidth,
),
)
: Image.file(
File(_imageInstance.image!.path),
),
),
))

Add a consumer widget on top of widgets that you want to rebuild
Center(
child: Consumer<GetThumbnailImage>(
builder:(context, model, child) => model.image == null
? SizedBox(
width: 200,
height: 200,
child: FadeInImage(
image: NetworkImage(apiBase +
productId.toString() +
apiLaterPart),
placeholder: AssetImage(
"assets/images/Otherimages/addvideo.jpg"),
imageErrorBuilder:
(context, error, stackTrace) {
return Image.asset(
'assets/images/Otherimages/addvideo.jpg',
fit: BoxFit.fitWidth);
},
fit: BoxFit.fitWidth,
),
)
: Image.file(
File(model.image!.path),
),
),
),

made this changes to your code and try
class GetThumbnailImage extends ChangeNotifier {
PickedFile? _image;
PickedFile get image => _image;
Future setImage(var file) async {
_image = file;
notifyListeners();
}
}

The reason is "dialog is not part of the widget tree"
Your code is not complete to help but here are some suggestions.
Solution 1.
You can pass the context of the parent widget to the dialog box, So the dialog box should also behave like a part of the widget tree
Solution 2. (Better)
Use something like this in the showDialogBox method...
final image = context.watch<GetThumbnailImage>();
ChangeNotifierProvider.value(
value: image
child: Dialog(...)
);
Now you can use instance of provider in the dialog box and it will also rebuild the widget tree to update the UI

Related

How to save an edited image in flutter (Edits on the image was done using ColorFiltered)?

I'm new to flutter and trying to build an editing app using ColorFiltered Class in flutter the changes are reflected while editing the image but not reflected when I try to save the image to the gallery (Only the unedited image is getting saved).
Below is the code which helps to display the image while editing.
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width,
child: AspectRatio(
aspectRatio: 1,
child: buildImage(),
),
),
],
)
),
Below is the code inside the buildImage() function which is called being called.
Widget buildImage() {
return ColorFiltered(
colorFilter: ColorFilter.matrix(calculateContrastMatrix(con)),
child: ColorFiltered(
colorFilter: ColorFilter.matrix(calculateSaturationMatrix(sat)),
child: Image.file(
_imageFile,
height: 400,
width: 250,),
),
);
}
And below is the code where I'm trying to savw the image.
TextButton(
child: const Text('Save Image'),
onPressed: () async {
final imageBytes = await _imageFile.readAsBytes();
final result = await ImageGallerySaver.saveImage(imageBytes);
},
Note I'm trying to update the _imageFile using setState.
void initState() {
super.initState();
_imageFile = File(widget.image.path);
}
Thank you.
I want the changes made through ColorFiltered class to be reflected on the saved image also.

setState() does not update Image widget after data is passed back from another screen

I currently have a screen where if you type into a search field it navigates you to a 2nd screen (ImageResults()) that displays images based upon the search keyword. When you select an image on the 2nd screen, the image url is passed back to the 1st screen and the image should be displayed as a preview.
I did a print statement print(image) to check if the 2nd screen successfully passed the url to the 1st screen and it did. However, the image widget is not updating even after calling setState() and keeps displaying the image when image == null is true when the print statement indicates that image is not null and contains the selected image url.
Any thoughts on what's going on? Thanks!
Widget build(BuildContext context) {
String? image;
TextEditingController _searchInputController = TextEditingController();
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
TextField(
controller: _searchInputController,
textInputAction: TextInputAction.search,
onSubmitted: (String keyword) async {
var data = await getPics(_searchInputController.text);
final _image = await Navigator.of(context).push(createRoute(
ImageResults(_searchInputController.text, data['photos'])));
setState(() {
_searchInputController.text = '';
image = _image;
});
print(image);
},
),
image == null
? ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/activity.jpg',
height: 200,
width: MediaQuery.of(context).size.width * .6,
fit: BoxFit.cover,
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
image!,
height: 200,
width: MediaQuery.of(context).size.width * .6,
fit: BoxFit.cover,
),
)
],
),
),
);
}
You need to move your variable declarations outside the build function. Because if your image variable is inside the build function, it will be set to null after your setState()
String? image;
Widget build(BuildContext context) {
//...
}
I'd recommend you do that for your TextEditingController as well

Putting a CircularProgressIndicator below each image

There are a lot of such stack elements on the page:
Stack(
children: <Widget>[
SizedBox(
height: 150,
width: 150,
child: Center(child: CircularProgressIndicator()),
),
Center(child: _getImage(imagePath)),
],
),
So there will be a CircularProgressIndicator below each image. Does it reduce the ui speed? When the Progress Indicator widget is ovelapped by the other widget will be disabled then?
First off you may use const on the constructor for better performance. Secondly, if you want to show a progress indicator when the image is loading maybe try using a FutureBuilder to check if the image is loading, if so return a CircularProgressIndicator else return the image.
But generally speaking flutter should be able to render tons of CircularProgressIndicator and yes it will still render the CircularProgressIndicator even if your image is shown and will therefore take up resources. If you dont want it to show and take resources you could warap it in a Visablity widget but I highly recommend using a futurebuilder so you can maintain the state of the image loading.
I think you are using progress loaders for image loading state, but I'd go with a package.
extended_image: ^4.1.0 seems like a good one and check the code below. It allows you to interfere in Loading State of the image, and you can put whatever widget in place of that state. This package does more than your intended use as well.
ExtendedImage.network(
url,
width: ScreenUtil.instance.setWidth(600),
height: ScreenUtil.instance.setWidth(400),
fit: BoxFit.fill,
cache: true,
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
_controller.reset();
return Image.asset(
"assets/loading.gif",
fit: BoxFit.fill,
);
break;
///if you don't want override completed widget
///please return null or state.completedWidget
//return null;
//return state.completedWidget;
case LoadState.completed:
_controller.forward();
return FadeTransition(
opacity: _controller,
child: ExtendedRawImage(
image: state.extendedImageInfo?.image,
width: ScreenUtil.instance.setWidth(600),
height: ScreenUtil.instance.setWidth(400),
),
);
break;
case LoadState.failed:
_controller.reset();
return GestureDetector(
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset(
"assets/failed.jpg",
fit: BoxFit.fill,
),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Text(
"load image failed, click to reload",
textAlign: TextAlign.center,
),
)
],
),
onTap: () {
state.reLoadImage();
},
);
break;
}
},
)

How can I make a Card height extend based on content?

I am creating a Card which has a header and a grid view of photos. Below is the code:
_getImageWidget(Post post, AppConfig config) {
if (post.photoPaths != null && post.photoPaths.length > 0) {
var url = config.imagePathDomain + post.photoPaths[0];
try {
return Expanded(
child: GridView.count(
crossAxisCount: 3,
shrinkWrap: false,
children: post.photoPaths.map<Widget>((String path) {
return CachedNetworkImage(
imageUrl: url,
);
}).toList()));
} catch (err) {
return Container();
}
}
return Container();
}
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
return BlocBuilder<UserInfoBloc, UserInfo>(builder: (_, userInfo) {
return Container(
width: MediaQuery.of(context).size.width,
child: Card(
child: Column(children: <Widget>[
Row(
children: <Widget>[
IconButton(
iconSize: 30,
icon: roundImage(post.userPicture, Icon(Icons.person)),
onPressed: () {},
),
Text('#${userInfo.username}')
],
),
this._getImageWidget(post, config),
])));
});
}
The header in the Card is a Row includes a IconButton and Text.
The body of the Card is a gridview which includes a few photo.
Below is the screenshot when I run the code. You can see that the photo is shown only a half. And I can scroll vertically on the grid view. The number of photos is dynamic which means there could be many rows of photos in the GridView. How can I make the Card extend based on its children?
By simply setting your CachedNetwork image to use a fit: BoxFit.cover that will resize to fill the content while preserving ratio (this means you may lose some of its details) or fit: BoxFit.contain that will try to be as big as possible while containing the entire source (image) within the bounds.
If that doesn't help as well (as I'm not seeing the entire tree so I'm not sure about the ancestors of your Card) you can also replace the return of your BlocBuilder's child to be a FittedBox instead of a Container and apply the same logic for the fit property, but for the whole card instead.
Try using a fixed size container and using the BoxFit property on the container.
Something like this :
new Container(
width: 80.0,
height: 80.0,
decoration: new BoxDecoration(
shape: BoxShape.rectangle,
image: new DecorationImage(
fit: BoxFit.fill,
image: CachedNetworkImageProvider(url),
),
),
),
Edit : Try to remove itemExtent from ListView.builder

How to show Hero Animation on a NetworkImage while switching from one screen to another?

In my flutter project, in one screen I show some list of data(including image) from API call. In that list I have wrapped the NetworkImage with Hero and added the productId as the tag.
Here's the code-
Expanded(
child: Hero(
tag: '${snapshot.data[position].products[index].id}',
child: FadeInImage(
width: 130.0,
height: 186.0,
placeholder: AssetImage('assets/placeholder.jpg'),
image:
NetworkImage(snapshot.data[position].products[index].image),
),
),
),
While switching to next screen, I pass the entire object which I get from API which following code-
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
snapshot.data[position].products[index])));
Now, in next Screen(where I have passed the entire object product), I have wrapped my NetworkImage with Hero and used the products.id as tag.
Here's the code-
Padding(
padding: EdgeInsets.only(top: ScreenUtil.instance.setHeight(15)),
child: Hero(
tag: '${products.id}',
child: FadeInImage(
height: ScreenUtil.instance.setHeight(330),
fit: BoxFit.fill,
placeholder: AssetImage('assets/placeholder.jpg'),
image: NetworkImage(
"${products.image}",
),
),
),
),
Now the problem is ------
After running the project, when I press one item from the list(where the NetworkImage includes), it shows black in the next screen and showing the following error in the console-
I have followed the same way described in some documentation that each items for animation should have the same tag name, but in my case it is not working. Showing that-
There are multiple heroes that share the same tag within a subtree.
So, I need a solution to solve this issue.
Save Network image file to a File variable then pass it throw pages (u can use file path as tag for hero and use file)
Retrieve data from ur database save to URL variable in my case it is just 'String url'(I'm getting it from another widget so I used widget.url)
from 1st screen
Hero(
tag: 'anything' + widget.url,
child: CircleAvatar(
backgroundImage: widget.url!= null
? ExtendedNetworkImageProvider(
widget.url,
)
: null,
radius: 23,
),
Navigator.of(context).push(TransparentRoute(
builder: (BuildContext context) => SecondScreen(
image: widget.url,
)));
(image URL is unique so we can use it as tag for hero)
2nd screen
class SecondScreen extends StatefulWidget {
final image;
SecondScreen(
{this.image});
#override
_SecondScreenState createState() => _SecondScreenState();
}
Hero(
tag: 'anything' + widget.image,
child: CircleAvatar(
backgroundImage: widget.image!= null
? ExtendedNetworkImageProvider(
widget.image
)
: null,
radius: 23,
),