How to initialize CameraController with SharedPreferences?

Issue

First I have initialized my camera controller (camera: ^0.9.4+11) like this and it works:

class TakePictureScreen extends StatefulWidget {
  final CameraDescription camera;

  const TakePictureScreen({required Key key, required this.camera}) 
    : super(key: key);

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

class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    _controller = CameraController(
      widget.camera,
      ResolutionPreset.max, // TODO: this should come from SharedPreferences
    );
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          return (snapshot.connectionState == ConnectionState.done)
            ? CameraPreview(_controller)
            : Text("");
        }
      ),
    );
  }
}

But now I want to load the ResolutionPreset dynamically from SharedPreferences (shared_preferences: ^2.0.13).
What’s a good way to do this?

I failed when trying it like this (adding some variables and changing initState method):

final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
late Future<int> _resolutionIndex;
List<ResolutionPreset> resolutions = ResolutionPreset.values.toList(growable: false);
static const String sharedPrefResolution = "resolution";

@override
void initState() {
  super.initState();
  _resolutionIndex = _prefs.then((SharedPreferences prefs) {
    int resolutionIndex = prefs.getInt(sharedPrefResolution) ?? (resolutions.length - 1);
    _controller = CameraController(
      widget.camera,
      resolutions[resolutionIndex],
    );
    _initializeControllerFuture = _controller.initialize();
    return resolutionIndex;
  });
}  

Getting the error: LateInitializationError: Field '_initializeControllerFuture@19039262' has not been initialized.

Solution

initState can’t be an async method, and getting a value from SharedPreferences is an async function. You can’t use await (or then) in initState, I mean you can use but the code execution will not wait for this to complete. So what happens here is that your build method will run earlier than the future getting the value from SharedPreferences completes. And as I presume your _initializeControllerFuture is marked as late, so when your build tries to use it, it is still null, and that will get you this error.

The common way to solve this issue is to use a FutureBuilder. Get the values from SharedPreferences with FutureBuilder, display a progress indicator while it is being loaded (it will be quick so if you think you can skip this part), and then when you get the value from it, build your widget using the value coming from SharedPreferences, and initialize CameraController only after this.

Answered By – Peter Koltai

Answer Checked By – Mildred Charles (FlutterFixes Admin)

Leave a Reply

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