How to properly precache images for stateless widgets?

Viewed 1819

I'm trying to avoid the image loading delay that makes it pop up in my stateless SplashScreen but with no luck. I tried to load the image in main() and pass it in to the constructor but had no changes.

I read quite a few answers on the same problem, and all post to use precacheImage in didChangeDependencies in extend State class of a Stateful Widget, so I tried it but still the result is the same even following answers examples and this article https://alex.domenici.net/archive/preload-images-in-a-stateful-widget-on-flutter.

class SplashScreen extends StatefulWidget {
  @override
  _SplashScreenState createState() => _SplashScreenState();
  
}

class _SplashScreenState extends State<SplashScreen> {

  Image logo;

  @override
  void initState() {
    super.initState();
    logo = Image.asset(
      'assets/notification.png',
      height: 170,
      width: 170,
    );
  }

    @override
      void didChangeDependencies() {
      super.didChangeDependencies();
      precacheImage(logo.image, context);

    }
  @override
  Widget build(BuildContext context) {


    return Scaffold(
      backgroundColor: Colors.redAccent,
      body: Center(
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              SizedBox(
                height: 150,
              ),
              logo,
              SizedBox(
                height: 60,
              ),
              Expanded(
                child: Text(
                  AppLocalizations.instance.text('Splash screen message'),
                  textAlign: TextAlign.center,
                  style: TextStyle(
                      color: Colors.white,
                      fontSize: 25,
                      fontWeight: FontWeight.w500,
                      letterSpacing: 1.5),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

I found a workaround as shown in my answer, but the problem persists if you don't have a stateful widget that pre caches images and pass them to the screen that uses them.

In my website for example I set my own Navigator service so screens don't get returned from a stateful widget:

Route generator

Route<dynamic> generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case HomeRoute:
      return _getPageRoute((HomePage()));
    case AboutRoute:
      return _getPageRoute(AboutPage());
    case RetailerAccessRoute:
      return _getPageRoute(RetailerAccess());
  }
}

So I set up two intermediate pages HomePageImagePreloader and AboutPageImagePreloader and returned those instead:

Route generato new

Route<dynamic> generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case HomeRoute:
      return _getPageRoute((HomePageImagePreloader()));
    case AboutRoute:
      return _getPageRoute(AboutPageImagePreloader());
    case RetailerAccessRoute:
      return _getPageRoute(RetailerAccess());
  }
}

AboutPageImagePreloader :

class AboutPageImagePreloader extends StatefulWidget {
  @override
  _AboutPageImagePreloaderState createState() => _AboutPageImagePreloaderState();
}

class _AboutPageImagePreloaderState extends State<AboutPageImagePreloader> {
  Image bgImageDesktop;
  Image bgImageMobile;

  @override
  void initState() {
    super.initState();
    bgImageDesktop = Image.asset('assets/aboutUsDesktopBg.jpg');
    bgImageMobile = Image.asset('assets/aboutUsMobileBg2.jpg');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    precacheImage(bgImageDesktop.image, context);
    precacheImage(bgImageMobile.image, context);

  }
  @override
  Widget build(BuildContext context) {
    return AboutPage(bgImageDesktop: bgImageDesktop, bgImageMobile: bgImageMobile);
  }
}

Added the necessaries images to 'HomePage' and AboutPage constructors but when website load up, background first bg image stil hasn't loaded.. so my workaround isn't working on web..

I can't believe that such a basic operation as loading an image has to be this complicated.. makes me think Flutter ain't such a good choice after all.. DO you know if pre caching isn't working on web yet? I'm on the latest dev channel..

Many thanks.

1 Answers

One way to make precacheImage method work in conjunction to my first try would be to leave SplashScreen stateless. Then main() child ( usually App() ) will be stateful to precache all needed images and pass them in screens through a final bgImage property in the constructor. This way it all loads up with no flickering bg images.

main()'s child :

class _FixitState extends State<Fixit> {
  final FirebaseAnalytics analytics = FirebaseAnalytics();
  Image logo;
  Image loginBg;

  @override
  void initState() {
    super.initState();
    logo = Image.asset('assets/notification.png');
    loginBg = Image.asset('assets/mainBg.png');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    precacheImage(logo.image, context);
    precacheImage(loginBg.image, context);

  }

  @override
  Widget build(BuildContext context) {

...

home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
        builder: (context, state) {

          if (state is Unauthenticated) {
            return LoginScreen(userRepository: widget._userRepository, bgImage: loginBg,);
          }

...

return SplashScreen(logo: logo);
...

SplashScreen (stateless):

class SplashScreen extends StatelessWidget {
  final Image logo;

  const SplashScreen({Key key, @required this.logo}) : super(key: key);

  @override
  Widget build(BuildContext context) {


    return Scaffold(
      backgroundColor: Colors.redAccent,
      body: Center(
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              SizedBox(
                height: 150,
              ),
              Image(image: logo.image,height: 170,
                width: 170,),
              SizedBox(
                height: 60,
              ),
              Expanded(
                child: Text(
                  AppLocalizations.instance.text('Splash screen message'),
                  textAlign: TextAlign.center,
                  style: TextStyle(
                      color: Colors.white,
                      fontSize: 25,
                      fontWeight: FontWeight.w500,
                      letterSpacing: 1.5),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Related