Vincenzo
Vincenzo

Reputation: 6458

How to properly precache images for stateless widgets?

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.

Upvotes: 5

Views: 4134

Answers (1)

Vincenzo
Vincenzo

Reputation: 6458

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),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Upvotes: 2

Related Questions