I'm transitioning from
Hero(
tag: imageModel.url,
child: CachedNetworkImage(
imageUrl: imageModel.url,
fit: BoxFit.cover,
),
)
to
final item = widget.galleryItems[index];
PhotoViewGalleryPageOptions(
imageProvider: CachedNetworkImageProvider(
item.url,
),
initialScale: PhotoViewComputedScale.contained,
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 1.2,
heroAttributes: PhotoViewHeroAttributes(tag: item.url),
);
The first transition doesn't use hero animation. When I navigate back it starts working. All the subsequent transitions use hero animation.
The issue doesn't happen when I use plain NetworkImage/Image.network provider/widget
Video demonstrating the issue:

If you add a placeholder to your CachedNetwork image does it work?
No, still same issue. Tried this:
Hero(
tag: imageModel.url,
child: CachedNetworkImage(
placeholder: (context, _) => Container(
color: Colors.red,
),
imageUrl: imageModel.url,
fit: BoxFit.cover,
),
),
Being the same thing here.
Here's a minimal example showing how to use Hero with CachedNetworkImage. I have not been able to replicate the problem you are experiencing:
https://gist.github.com/MichaelMarner/52c18de80c8813628001cc725beefb0b
Are you able to modify the example and replicate the problem?
The modified Gist with error reproducible is below. It's using photo_view library.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(
title: 'CachedNetworkImage Hero',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(),
);
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('CachedNetworkImage')),
body: _gridView(),
);
}
_gridView() {
return GridView.builder(
itemCount: 250,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
final url = 'https://loremflickr.com/500/500/music?lock=$index';
return InkWell(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => Scaffold(
appBar: AppBar(title: Text('Detail')),
body: PhotoViewGallery.builder(
scrollPhysics: const ClampingScrollPhysics(),
builder: _buildItem,
itemCount: 250,
),
),
),
),
child: Hero(
tag: url,
child: CachedNetworkImage(
imageUrl: url,
placeholder: _loader,
errorWidget: _error,
),
),
);
});
}
PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
final url = 'https://loremflickr.com/500/500/music?lock=$index';
return PhotoViewGalleryPageOptions(
// imageProvider: NetworkImage(
// url,
// ),
imageProvider: CachedNetworkImageProvider(
url,
),
initialScale: PhotoViewComputedScale.contained,
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 1.2,
heroAttributes: PhotoViewHeroAttributes(tag: url),
);
}
Widget _loader(BuildContext context, String url) => Center(
child: CircularProgressIndicator(),
);
Widget _error(BuildContext context, String url, dynamic error) {
return Center(child: const Icon(Icons.error));
}
}
ok so this is more to do with how photo_view implements their Hero, than this package. From their docs:
To use within an hero animation, specify heroAttributes. When heroAttributes is specified, the image provider retrieval process should be sync. (source)
In their implementation, if the image is not completely loaded by the time the first call to build then the Hero is never actually put into the PhotoView, and so the Hero animation cannot happen. There are several related issues regarding this limitation:
https://github.com/renancaraujo/photo_view/issues/191
https://github.com/renancaraujo/photo_view/issues/128
However, in testing I managed to find a workaround. Instead of using a CachedNetworkImage widget and transitioning to a PhotoView, use a regular Image with a CachedNetworkImageProvider. The Hero transition works in the following example:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(
title: 'CachedNetworkImage Hero',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(),
);
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('CachedNetworkImage')),
body: _gridView(),
);
}
_gridView() {
return GridView.builder(
itemCount: 250,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
final url = 'https://loremflickr.com/500/500/music?lock=$index';
return InkWell(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => Scaffold(
appBar: AppBar(title: Text('Detail')),
body: PhotoView(
imageProvider: CachedNetworkImageProvider(url),
heroAttributes: PhotoViewHeroAttributes(
tag: url,
),
),
),
),
),
child: Hero(
tag: url,
child: Image(
image: CachedNetworkImageProvider(
url,
),
),
),
);
});
}
}
So I guess the next question is: why does CachedNetworkImageProvider work differently to CachedNetworkImage?
Any updates on this issue? I am using a thumbnail of a photo. When clicked, the real photo shows and I would like to use the thumbnail as a placeholder (and use a hero for that) until the real photo is fully loaded.
Just to elaborate on what I am trying to achieve (which is not working)
Hero(
tag: "myTag",
child: CachedNetworkImage(
imageUrl: widget.imageURL,
placeholder: (context, url) =>
Image.network(widget.thumbnail),
errorWidget: (context, url, error) => Icon(Icons.error)),
),
and on the calling end:
Hero(
tag: "myTag",
child: CachedNetworkImage(
imageUrl: widget.thumbnail,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error)),
)
@Navil This is most likely a different issue - As you are using Image.network for your placeholder, Flutter is going to redownload the thumbnail.
Try using another CachedNetworkImage as your placeholder:
Hero(
tag: "myTag",
child: CachedNetworkImage(
imageUrl: widget.imageURL,
placeholder: (context, url) =>
CachedNetworkImage(imageUrl: widget.thumbnail),
errorWidget: (context, url, error) => Icon(Icons.error)),
),
(note, untested)
My code block is working. Not depent PhotoView package.
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(10)),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return PhotoFullScreen(
medya.fileOwnerUrl, medya.fileOwnerUrl.toString());
}));
},
child: Container(
width: 180,
color: Colors.black12,
child: Hero(
tag: medya
.fileOwnerUrl,
child: CachedNetworkImage(
imageUrl: medya.fileOwnerUrl,
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) =>
Icon(Icons.error),
fit: BoxFit.cover,
),
))),
);
class PhotoFullScreen extends StatelessWidget {
final String photoURL;
final String heroTag;
const PhotoFullScreen(this.photoURL, this.heroTag, {Key key})
: super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: heroTag,
child: CachedNetworkImage(
imageUrl: photoURL,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
),
),
),
onTap: () {
Navigator.pop(context);
},
onVerticalDragEnd: (s) {
Navigator.pop(context);
},
),
);
}
}
video demonstrating

The following approach is a feasible solution, the animation and image load transition both work properly.
Hero(
tag: url,
//Apply [FadeInImage] to provide loading transition
child: FadeInImage(
// Use [CachedNetworkImageProvider] to active the first animation
image: CachedNetworkImageProvider(url),
placeholder: AssetImage('PATH'),
),
),
PhotoView(
imageProvider: CachedNetworkImageProvider(url),
heroAttributes: PhotoViewHeroAttributes(
tag: url,
),
),
In 2.3.0-beta the widget uses the CachedNetworkImageProvider, so the issue with the hero should be fixed
I can't get the Hero animation to work with CachedNetworkImage (2.3.3) and PhotoView (0.10.3).
The code I use:
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(backgroundColor: Colors.transparent),
body: PhotoView(
imageProvider: CachedNetworkImageProvider(widget.coverImageUrl),
initialScale: PhotoViewComputedScale.contained,
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 1.8,
heroAttributes: const PhotoViewHeroAttributes(
tag: 'eventCoverImage',
transitionOnUserGestures: true,
),
),
),
),
);
},
child: Hero(
tag: 'eventCoverImage',
transitionOnUserGestures: true,
child: CachedNetworkImage(
imageUrl: imageUrl,
height: height,
fit: fit,
),
),
)
What if you just wrap a Hero around PhotoView? I don't know how PhotoView's heroAttributes work.
It does not work.
I didn't dig into PhotoView code yet, as the Hero animation is nice-to-have in my case.
Most helpful comment
ok so this is more to do with how
photo_viewimplements their Hero, than this package. From their docs:In their implementation, if the image is not completely loaded by the time the first call to
buildthen the Hero is never actually put into the PhotoView, and so the Hero animation cannot happen. There are several related issues regarding this limitation:https://github.com/renancaraujo/photo_view/issues/191
https://github.com/renancaraujo/photo_view/issues/128
However, in testing I managed to find a workaround. Instead of using a
CachedNetworkImagewidget and transitioning to aPhotoView, use a regularImagewith aCachedNetworkImageProvider. The Hero transition works in the following example:So I guess the next question is: why does
CachedNetworkImageProviderwork differently toCachedNetworkImage?