Home > database >  Flutter ProviderNotFound Error, I'm trying to load the URL from a different class
Flutter ProviderNotFound Error, I'm trying to load the URL from a different class

Time:11-29

I was trying to make a videoplayer that can be paused by tapping the screen (like YouTube Short) but I was having a tough time working with Provider as I'm still new to it I was unable to access methods in VideoPlayerScreen so I decided to make a VideoPlayerProvider class so I can update the URL attribute inside it through other classes but I'm getting the ProviderNotFound Exception

Can someone guide me what am I doing wrong here? Any Resources for learning provider in depth would be helpful too, Thanks in Advance.

Apologies if I was unable to frame my question properly `

import 'dart:async';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

void main() => runApp(ChangeNotifierProvider(
    create: (context) => VideoPlayerProvider(), child: const VideoPlayerApp()));

class VideoPlayerProvider extends ChangeNotifier {
  String url =
      'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
  late VideoPlayerController _controller = VideoPlayerController.network(url);

  String get getUrl => url;
  VideoPlayerController get getController => _controller;

  void newVid(String newUrl) {
    url = newUrl;
    notifyListeners();
  }

  void pp() {
    if (_controller.value.isPlaying) {
      _controller.pause();
    } else {
      _controller.play();
    }
    notifyListeners();
  }
}

class VideoPlayerApp extends StatelessWidget {
  const VideoPlayerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Video Player Demo',
      home: Stack(children: [ VideoPlayerScreen()]),
    );
  }
}

class VideoPlayerScreen extends StatefulWidget{
  const VideoPlayerScreen({super.key});

  @override
  State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen>{
  late VideoPlayerController _controller =
      context.watch<VideoPlayerProvider>().getController;
  late Future<void> _initializeVideoPlayerFuture;
  late String _url = context.watch<VideoPlayerProvider>().getUrl;

  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.network(_url);
    _initializeVideoPlayerFuture = _controller.initialize();
    _controller.setLooping(true);
    _controller.play();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
        future: _initializeVideoPlayerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return AspectRatio(
              aspectRatio: 9 / 18.45,
              child: VideoPlayer(_controller),
            );
          } else {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
        },
      ),
    );
  }
}

`

I was trying to update the URL of VideoPlayerScreen So I can update it from external methods

CodePudding user response:

Your code contained redundant initialization of variables and improper usage of the Provider. For a better understanding, you are suggested to go back to the documentation and examples of the Provider package.

However, I've removed all the redundancy from the code and tried to present a better usage of the Provider to you.

Revised code has been pasted below. I've tested it with the latest versions of the VideoPlayer and Provider packages and it is working perfectly fine. I've mentioned step wise detailed comments inside the code for you to understand what changes have been made and why. Some bonus steps have also been added and explained therein. You would notice that the provider is being fully used to initialize the controller and play, pause and change the video. Remember, there can be better ways of achieving this; however, for simplicity and relevance, your code has been modified wherever necessary, in order to achieve the required objective.

void main() => runApp(ChangeNotifierProvider(
    create: (context) => VideoPlayerProvider(), child: const VideoPlayerApp()));

class VideoPlayerProvider extends ChangeNotifier {
  String url =
      'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
  late VideoPlayerController _controller;

  /// STEP 1
  /// Defined this async method for use in the FutureBuilder. It contains the
  /// code required to initialize the controller and play the video.
  Future<void> initializeController() async {
    _controller = VideoPlayerController.network(url);
    await _controller.initialize();
    await _controller.setLooping(true);
    return _controller.play();
  }

  String get getUrl => url;

  VideoPlayerController get controller => _controller;

  /// BONUS STEP 3
  /// Removed the unnecessary notifyListeners() method and instead called
  /// initializeController() in order to load and play the new video.
  void newVid(String newUrl) {
    url = newUrl;
    initializeController();
    // notifyListeners();
  }

  /// BONUS STEP 2
  /// Removed the unnecessary notifyListeners() method.
  /// Since we are using the same instance of controller inside the widget, we
  /// don't need to define and notify the listeners
  void pp() {
    if (_controller.value.isPlaying) {
      _controller.pause();
    } else {
      _controller.play();
    }
    // notifyListeners();
  }
}

class VideoPlayerApp extends StatelessWidget {
  const VideoPlayerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Video Player Demo',
      home: Stack(children: const [VideoPlayerScreen()]),
    );
  }
}

class VideoPlayerScreen extends StatefulWidget {
  const VideoPlayerScreen({super.key});

  @override
  State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {

  late VideoPlayerProvider provider;

  /// STEP 4
  /// Used the same instance of controller as initialized in the provider
  VideoPlayerController get controller => provider.controller;

  @override
  void initState() {
    super.initState();

    /// STEP 2
    /// Lazy initialized the provider variable, for later use in the app
    provider = Provider.of<VideoPlayerProvider>(context, listen: false);

    /// STEP 3
    /// Removed all redundant code for initializing the controller
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
        /// STEP 5
        /// Used the provider's async method here instead of creating a new
        /// redundant one here
        future: provider.initializeController(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return AspectRatio(
              aspectRatio: 9 / 18.45,
              /// BONUS STEP 1
              /// Used a GestureDetector widget to listen to screen taps and
              /// called the pp() toggle method of the provider.
              child: GestureDetector(
                onTap: () => provider.pp(),
                child: VideoPlayer(controller),
              ),
            );
          } else {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
        },
      ),
    );
  }
}

Hope it helps!

  • Related