I\'ve created a stateful widget and its parent widget needs to call a function that lives in the child\'s state.
Specifically, I have a class PlayerContainer that create
While Darren Cole's answer above does not work correctly, there is an easy way to circumvent the immediate problem. Instead of
State<StatefulWidget> createState() => vpcs;
final VideoPlayerControllerState vpcs = VideoPlayerControllerState();
write:
State<StatefulWidget> createState(){
vpcs = VideoPlayerControllerState();
return vpcs;
}
VideoPlayerControllerState vpcs;
This way, the state gets re-written everytime createState()
is called, which avoids the Failed assertion: line 3819 pos 12: '_state._widget == null': is not true
errors.
Still, I guess this is a somewhat hackish solution - can somebody point out a better one?
With a simple application you could simply create the play button in the same Widget as the VideoPlayer. By combining the PlayerContainer with its parent you are increasing the size of the scope of the Widget State so that everything that need access to it is part of the single larger Widget.
The main ways that a child Widget can be influenced by an ancestor are: by being rebuilt with different parameters, or by listening to something that the ancestor changes. For the latter you can use an InheritedWidget somewhere about the child. If the child refers to the InheritedWidget it gets rebuilt when the IW changes. Another way is to listen to an event stream generated by the ancestor.
You may find it easiest to just build your whole page in a single build until this becomes unwieldy.
State management is the one thing they dropped the ball on with respect to this framework. I've used static variables (if I need to share data between states) and GlobalKeys (for just one quick, dirty solution) to get this done. We're supposed to use InheritedWidgets, it's just extremely out-of-the-way for something that should be simple. I usually just do this:
// top of code here - this is global
final videoPlayerKey = GlobalKey();
class VideoPlayerContainer extends StatelessWidget {
static VideoPlayerController videoPlayerController;
...
@override
Widget build(BuildContext context) {
videoPlayerController = VideoPlayerController(...);
// the static variable is empty until the container is built
return Container(
child: VideoPlayer(
child: PlayButton(onTap: () =>
videoPlayerKey.currentState.setState(
() => VideoPlayerContainer.videoPlayerController.play();
))
),
);
}
}
class VideoPlayer extends StatefulWidget {
final Key key = videoPlayerKey;
...
}
class VideoPlayerState extends State<VideoPlayer> {
...
}
We need to get videoPlayerKey's currentState to use setState() and re-run the build method so it knows to update, and then we can grab the controller for the player wherever it is stored using the static variable. It could be in VideoPlayer or anywhere else that's not here in VideoPlayerContainer, because it's static - it's just important that you assign the GlobalKey to whatever Widget will need to be rebuilt. It will work because whenever a user could tap the button, the static variable will have been set for any void to read by VideoPlayerContainer's build() method. For this method, it's important to note that it's more important that you attach the GlobalKey to the element that needs to be updated - you can put the static pageController literally wherever and set it from wherever within a build() or initState().
Notes: This will not work if you try to use multiple VideoPlayers in one layout, because GlobalKeys must be unique, and all VideoPlayers will be initialized with the same key. This is more of a dirty hack than anything. I am working on a more robust state management solution at the moment to solve stuff like this.
I know that I'm pretty late to the party, but I have something that I think might help. So, you need to do four (4) things in your VideoPlayerController
class:
1. Create an instance of your state class.
2. Create a method (play) which will be accessible in your PlayerContainer
class
3. In your method, use the VideoPlayerControllerState
instance to call the method in your state class.
4. Finally, when you createState
, do so using the instance that you already created.
class VideoPlayerController extends StatefulWidget {
final VideoPlayerControllerState vpcs = VideoPlayerControllerState();
void play() {
vpcs.play();
}
@override
State<StatefulWidget> createState() => vpcs;
}
As you see, the play
method uses vpcs
(the VideoPlayerControllerState instance) to call the play method already in your state class.
In your PlayerContainer class, use your member variable to call the play method.
class PlayerContainerState extends State<PlayerContainer> {
VideoPlayerController _vpc;
@override
void initState() {
super.initState();
_vpc = VideoPlayerController();
}
...
void _handlePressPlay(){
_vpc.play();
}
...
@override
Widget build(BuildContext context) {
return ... //your video player widget using _vpc as your VideoPlayerController
_vpc,
);
}
}
You can call _handlePressPlay()
from the onPressed
method of your play button. Alternatively, just put _vpc.play()
in the onPressed
method. Your choice :-).