Flutter change slider thumb to image

Issue

I want to create something like this.

enter image description here

I have managed to create it but anytime I start the app, I get LateInitializationError which says LateInitializationError: Field 'customImage' has not been initialized. before the slider shows.

This is my code. What am I doing wrong? I also tried the flutter xlider package but it doesn’t work anymore since it doesn’t support null safety. If you have a better way of changing the slider thumb, it will be greatly appreciated.

import 'package:flutter/material.dart';
import 'dart:ui' as ui;

import 'package:flutter/services.dart';

class CustomSlider extends StatefulWidget {
  const CustomSlider({Key? key}) : super(key: key);

  @override
  _CustomSliderState createState() => _CustomSliderState();
}

class _CustomSliderState extends State<CustomSlider> {
  late ui.Image customImage;
  double sliderValue = 0.0;

  Future<ui.Image> loadImage(String assetPath) async {
    ByteData data = await rootBundle.load(assetPath);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    ui.FrameInfo fi = await codec.getNextFrame();

    return fi.image;
  }

  @override
  void initState() {
    loadImage('images/star.png').then((image) {
      setState(() {
        customImage = image;
      });
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SliderTheme(
      data: SliderThemeData(
        trackHeight: 28,
        inactiveTrackColor: Colors.grey.shade300,
        activeTrackColor: const Color(0xFFFFE900),
        thumbShape: SliderThumbImage(customImage),
      ),
      child: Slider(
        value: 50,
        min: 0,
        max: 100,
        onChanged: (value) {},
      ),
    );
  }
}

class SliderThumbImage extends SliderComponentShape {
  final ui.Image image;

  SliderThumbImage(this.image);

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return const Size(0, 0);
  }

  @override
  void paint(PaintingContext context, Offset center,
      {required Animation<double> activationAnimation,
      required Animation<double> enableAnimation,
      required bool isDiscrete,
      required TextPainter labelPainter,
      required RenderBox parentBox,
      required SliderThemeData sliderTheme,
      required TextDirection textDirection,
      required double value,
      required double textScaleFactor,
      required Size sizeWithOverflow}) {
    final canvas = context.canvas;
    final imageWidth = image.width;
    final imageHeight = image.height;

    Offset imageOffset = Offset(
      center.dx - (imageWidth / 2),
      center.dy - (imageHeight / 2),
    );

    Paint paint = Paint()..filterQuality = FilterQuality.high;

    canvas.drawImage(image, imageOffset, paint);
  }
}

Solution

initState runs before the widget is built, but can’t be an async function, so customImage = image; will almost surely run later than the build function. And since you are using customImage in the build function, and customImage is marked as late, you receive this error.

You can remove late keyword, make customImage nullable, and in the build method check for its value. If it is null (image not loaded yet), display a progress indicator for example.

But this is not perfect, because depending on the time that image loading takes, there is a chance that setState is invoked while the widget is being built, which would trigger another error.

Although you can solve the above with an addPostFrameCallback, the best way is to use a FutureBuilder, load the image first and build your widget afterwards:


late Future<ui.Image> _loadImage;

@override
void initState() 
  super.initState();
  _loadImage = loadImage();
}

@override
Widget build(BuildContext context) {
  return FutureBuilder<ui.Image>(
      future: _loadImage,
      builder: (context, snapshot) {
        if (snapshot.hasData || snapshot.data != null) {
          return SliderTheme(
            data: SliderThemeData(
              trackHeight: 28,
              inactiveTrackColor: Colors.grey.shade300,
              activeTrackColor: const Color(0xFFFFE900),
              thumbShape: SliderThumbImage(snapshot.data!),
            ),
            child: Slider(
              value: 50,
              min: 0,
              max: 100,
              onChanged: (value) {},
            ),
          );
        }
        // progress indicator while loading image,
        // you can return and empty Container etc. if you like
        return CircularProgressIndicator();
      });
}

Answered By – Peter Koltai

Answer Checked By – Katrina (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *