Result of consuming a Future with future.then() seems to only live inside the .then function()

778 views Asked by At

I am building an app as a project for university and one of the requirements is to play videos within the app.

I have links to exercise videos (bicep curls and so on) stored in a column of a table in SQLite.

I am using Moor in order to interact with the database.

I have the following screen where I am trying to have the video referred in the link from the database play:

class ExerciseVideoTab extends StatefulWidget {
    final int exerciseId;

    ExerciseVideoTab(this.exerciseId);

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

class _ExerciseVideoTabState extends State<ExerciseVideoTab> {
    VideoPlayerController _controller;
    Future<void> _initializeVideoPlayerFuture;
    String _exerciseVideoLink;
    
    @override
    void initState() {
      super.initState();

      locator<MoorDB>().getExerciseById(widget.exerciseId).then((value) => 
                        _exerciseVideoLink = value.exerciseVideoLink);
      _controller = VideoPlayerController.network(_exerciseVideoLink.toString());
      _initializeVideoPlayerFuture = _controller.initialize();

      print(_exerciseVideoLink); // prints null for some reason
  }

  @override
  void dispose() {
    // Ensure disposing of the VideoPlayerController to free up resources.
    _controller.dispose();

    super.dispose();
  }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
      body: Center(child: FutureBuilder(
        future: _initializeVideoPlayerFuture,
        builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
            // If the VideoPlayerController has finished initialization, use
            // the data it provides to limit the aspect ratio of the video.
              return AspectRatio(
                aspectRatio: _controller.value.aspectRatio,
                // Use the VideoPlayer widget to display the video.
                child: VideoPlayer(_controller),
              );
            } else {
              // If the VideoPlayerController is still initializing, show a
              // loading spinner.
                return Center(child: CircularProgressIndicator());
              }
            }
         )
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Wrap the play or pause in a call to `setState`. This ensures the
          // correct icon is shown.
          setState(() {
            // If the video is playing, pause it.
            if (_controller.value.isPlaying) {
              _controller.pause();
            } else {
              // If the video is paused, play it.
              _controller.play();
            }
          });
        },
        // Display the correct icon depending on the state of the player.
        child: Icon(
          _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
        ),
      ),
    );
  }
}

I am trying to consume the Future<Exercise> that is returned by the getExerciseById(int id) method and assign the exerciseVideoLink column value to the local _exerciseVideoLink and then use that String to initialize the VideoPlayerController with the link contained within.

The implementation of getExerciseById(int id) is the following:

    Future<Exercise> getExerciseById(int id) {
        return (select(exercises)..where((exercise) => exercise.exerciseId.equals(id))).getSingle();
    }

My problem right now is that after consuming the Future<Exercise> and assigning its exerciseVideoLink attribute to the local String variable, the variable becomes null as soon as the .then((value) => ... function is over and thus, the initialization of the VideoPlayerController fails because the URI is null.

Why is that? How can I make it so that I can consume the Future<Exercise> and use its exerciseVideoLink in order to pass it to the VideoPlayerController?

1

There are 1 answers

1
cameron1024 On

Your _controller depends on the result of getExerciseById() so you need to wait for that Future to complete before you can assign it. You may find async/await syntax a bit easier to read when working with lots of nested Futures.

An example implementation could be:

@override
void initState() {
  super.initState();
  _init();  // split out a separate method, initState cannot be async
}

Future<void> _init() async {
  final exercise = await locator<MoorDB>().getExerciseById(widget.exerciseId);
  _controller = VideoPlayerController.network(exercise.exerciseVideoLink.toString());
  _initializeVideoPlayerFuture = _controller.initialize();
}