I am trying to get user information from an API. For this, I created a user object. I want to call the function and store it in a user. But the problem is, that I cannot use await and wait till all the data is there.
So instead of async and await, I tried to use .then and fill userInfo with the data. But now the email value is not showing. It is showing 'loading...'.
If I use Future I cannot do user.email.
Is it better to use FutureBuilder? Or try and use Async and Await (the call takes 2.5 seconds)
Here is the code
class Addressbook extends StatefulWidget {
const Addressbook({Key? key}) : super(key: key);
#override
State<Addressbook> createState() => _AddressbookState();
}
class _AddressbookState extends State<Addressbook> {
User? userInfo;
Future<User> getUserInformation() async {
User user = await UserService().getUserById(12345);
return user;
}
#override
void initState() {
// TODO: implement initState
getUserInformation().then((response) {
userInfo = response;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Mijn adresboek'),
centerTitle: true,
elevation: 0.5,
titleTextStyle: const TextStyle(
fontSize: 21,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
body: Text(userInfo?.email ?? "loading..."),
);
}
}
What do I want to archive?
I want to display user data on the screen. In this case I want to show the email.
What is the problem?
User is not filled with data on init (probably because it is still loading to get the data).
My question
How can I solve this problem? Is async await a solution or should I use FutureBuilder? Can you give me a working sample?
Thanks for helping!
You can use futurebuilder or you can use in initstate to fetch api
#override
void initState() {
// TODO: implement initState
Future.delayed(Duration(seconds: 10), () {
setState(() {
userinfo = "response";
});
// userinfo = "response";
});
super.initState();
}
Inside the widget
Text(userinfo != null ? userinfo.toString() : "loading..."),
SAmple Code
import 'package:flutter/material.dart';
//import 'package:pucon/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? userinfo = null;
String? userinfo2 = null;
#override
void initState() {
// TODO: implement initState
Future.delayed(Duration(seconds: 10), () {
setState(() {
userinfo = "response";
});
// userinfo = "response";
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ListView(
shrinkWrap: true,
children: [
Text(""),
Text(""),
Text(userinfo != null ? userinfo.toString() : "loading..."),
Row(
children: [
FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Container(
child: Text(userinfo2.toString()), );
} else {
return SizedBox(
child: CircularProgressIndicator(),
height: 45,
width: 45,
);
// else
// return Container(
// child: CircularProgressIndicator(),
// height: 45,
// width: 45,
// );
}
},
future: _future(),
),
],
)
// InsertData(),
],
),
);
}
_future() async {
await Future.delayed(Duration(seconds: 5), () {
userinfo2 = "Completed";
// setState(() {
//
// });
// userinfo = "response";
});
}
}
Related
I am building a method that the user can select a prefered profile picture to show arround the app, using provider package. I used shared_preferences to save the profile picture preferences on locally as a int value. And it worked, means I can save the profile picture to local system. But the problem is, the provider package completely became useless in this case, because I have to convert the widget to statefull and call the setState method when ever I insert a profilePicture widget inside the widget tree. And even the profilePicture widget in the HomeScreen not updating this way. I want to know how can I use the provider package for this issue instead of using statefulWidgets.
watch the Gif or video
This is the Provider class I created:
class ProfilePicProvider with ChangeNotifier {
ProfilePicPref profilePicPreferences = ProfilePicPref();
int _svgNumber = 1;
int get svgNumber => _svgNumber;
set svgNumber(int value) {
_svgNumber = value;
profilePicPreferences.setProfilePic(value);
notifyListeners();
}
void changePic(int val) {
_svgNumber = val;
profilePicPreferences.setProfilePic(val);
notifyListeners();
}
}
This is the sharedPreferences class
class ProfilePicPref {
static const PRO_PIC_STS = 'PROFILESTATUS';
setProfilePic(int svgNo) async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
profilePref.setInt(PRO_PIC_STS, svgNo);
}
Future<int> getProfilePicture() async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
return profilePref.getInt(PRO_PIC_STS) ?? 1;
}
}
This is the image selection screen and save that data to sharedPreferences class
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
This is the HomeScreen which is not updating the profile image whether it is statefull or stateless
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final proPicProvider = Provider.of<ProfilePicProvider>(context);
return Scaffold(
body:
Column(
children: [
Row(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proPicProvider.svgNumber}.svg'),
],
),
],
),
);
}
}
example:
I have to convert the widget to statefull and call setState method to get the current profile picture from sharedPreferences. You may find this screen from the GIF I provided.
class Progress extends StatefulWidget {
const Progress({Key? key}) : super(key: key);
#override
State<Progress> createState() => _ProgressState();
}
class _ProgressState extends State<Progress> {
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SizedBox(
height: 130.0,
width: 130.0,
child: SvgPicture.asset(
'assets/svg/${proProvider.svgNumber}.svg'),
),
),
);
}
}
The problem is in _SelectProfilePictureState when you create new instance of your ChangeNotifier:
ProfilePicProvider proProvider = ProfilePicProvider();. It means you are not using the provider available across the context but creating new one every time. So when the value of your provider changed it has effect only inside _SelectProfilePictureState. Instead of creating new instance you must call it always using the context:
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
// [removed] ProfilePicProvider proProvider = ProfilePicProvider();
//removed
/*void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}*/
#override
Widget build(BuildContext context) {
//use provider from the context
final proProvider = Provider.of<ProfilePicProvider>(context,listen:true);
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
If you enter the application you may want send initially selected image to your provider:
Add parameter to the constructor of ProfilePicProvider:
ProfilePicProvider(SharedPreferences prefs): _svgNumber = prefs.getInt(ProfilePicPref.PRO_PIC_STS) ?? 1;
In main.dart:
Future<void> main()async{
WidgetsFlutterBinding.ensureInitialized();
var prefs = await SharedPreferences.getInstance();
runApp(
MultiProvider(
providers:[
ChangeNotifierProvider( create:(_) => ProfilePicProvider(prefs))
],
child: yourtopWidget
)
);
}
I want to control how I close specific dialogs in Flutter. I know if I call Navigator.of(context).pop() it will close the latest dialog.
However my situation is that I can have two dialogs opened at the same time in different order (a -> b or b -> a) and I want to explicitly close one of them.
I know that showDialog builder method provides a BuildContext that I can reference and do Navigator.of(storedDialogContext).pop() but that actually doesn't really help since this context shares same navigation stack.
Update: Vandan has provided useful answer. One solution is to use Overlay widget but it has its downsides, see this answer
My example is on dartpad.dev, example code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Completer<BuildContext>? _dialog1Completer;
Completer<BuildContext>? _dialog2Completer;
bool _opened1 = false;
bool _opened2 = false;
#override
void initState() {
super.initState();
Timer(const Duration(seconds: 3), () {
_openDialog1();
debugPrint('Opened dialog 1. Dialog should read: "Dialog 1"');
Timer(const Duration(seconds: 2), () {
_openDialog2();
debugPrint('Opened dialog 2. Dialog should read: "Dialog 2"');
Timer(const Duration(seconds: 3), () {
_closeDialog1();
debugPrint('Closed dialog 1. Dialog should read: "Dialog 2"');
Timer(const Duration(seconds: 5), () {
_closeDialog2();
debugPrint('Closed dialog 2. You should not see any dialog at all.');
});
});
});
});
}
Future<void> _openDialog1() async {
setState(() {
_opened1 = true;
});
_dialog1Completer = Completer<BuildContext>();
await showDialog(
barrierDismissible: false,
context: context,
routeSettings: const RouteSettings(name: 'dialog1'),
builder: (dialogContext) {
if (_dialog1Completer?.isCompleted == false) {
_dialog1Completer?.complete(dialogContext);
}
return CustomDialog(title: 'Dialog 1', timeout: false, onClose: _closeDialog1);
});
}
Future<void> _openDialog2() async {
setState(() {
_opened2 = true;
});
_dialog2Completer = Completer<BuildContext>();
await showDialog(
barrierDismissible: false,
context: context,
routeSettings: const RouteSettings(name: 'dialog1'),
builder: (dialogContext) {
if (_dialog2Completer?.isCompleted == false) {
_dialog2Completer?.complete(dialogContext);
}
return CustomDialog(title: 'Dialog 2', timeout: false, onClose: _closeDialog2);
});
}
Future<void> _closeDialog1() async {
final ctx = await _dialog1Completer?.future;
if (ctx == null) {
debugPrint('Could not closed dialog 1, no context.');
return;
}
Navigator.of(ctx, rootNavigator: true).pop();
setState(() {
_dialog1Completer = null;
_opened1 = false;
});
}
Future<void> _closeDialog2() async {
final ctx = await _dialog2Completer?.future;
if (ctx == null) {
debugPrint('Could not closed dialog 2, no context.');
return;
}
Navigator.of(ctx, rootNavigator: true).pop();
setState(() {
_dialog2Completer = null;
_opened2 = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
TextButton(onPressed: _openDialog1, child: const Text('Open 1')),
TextButton(onPressed: _openDialog2, child: const Text('Open 2')),
const Spacer(),
Align(
alignment: Alignment.bottomCenter,
child: Text('Opened 1? $_opened1\nOpened 2? $_opened2'),
),
],
),
),
);
}
}
class CustomDialog extends StatefulWidget {
const CustomDialog({
Key? key,
required this.timeout,
required this.title,
required this.onClose,
}) : super(key: key);
final bool timeout;
final String title;
final void Function() onClose;
#override
createState() => _CustomDialogState();
}
class _CustomDialogState extends State<CustomDialog>
with SingleTickerProviderStateMixin {
late final Ticker _ticker;
Duration? _elapsed;
final Duration _closeIn = const Duration(seconds: 5);
late final Timer? _timer;
#override
void initState() {
super.initState();
_timer = widget.timeout ? Timer(_closeIn, widget.onClose) : null;
_ticker = createTicker((elapsed) {
setState(() {
_elapsed = elapsed;
});
});
_ticker.start();
}
#override
void dispose() {
_ticker.dispose();
_timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.title),
content: SizedBox(
height: MediaQuery.of(context).size.height / 3,
child: Center(
child: Text([
'${_elapsed?.inMilliseconds ?? 0.0}',
if (widget.timeout) ' / ${_closeIn.inMilliseconds}',
].join('')))),
actions: [
TextButton(onPressed: widget.onClose, child: const Text('Close'))
],
);
}
}
If you were to run this code and observe console you can see steps being printed, on step #3 you can observe unwanted behaviour:
opened dialog 1 - OK
opened dialog 2 - OK
closed dialog 1 - not OK
I think I understand the problem, Navigator.of(dialogContext, rootNavigator: true) searches for nearest navigator and then calls .pop() method on it, removing the latest route/dialog on its stack.
I would need to remove specific dialog.
What would be the solution here? Multiple Navigator objects?
I highly suggest that in this case you use Overlay in Flutter. Overlays are rendered independently of widgets on the screen and have their own lifetimes. They appear when you ask them to and you can control when and which one of them should disappear at which time.
I am trying to develop an app that presents videos to the user. I am using VideoPlayerController for loading the videos, and ChewieController for the UI.
It works great, but when the user closes the app, the video stops. I would like the video to keep playing its audio even when closing the app/locking the device.
I couldn't find anything about it on the VideoPlayerController and in the ChewieController documentations.
Is this functionality possible in Flutter and Dart?
Thank you!
Unfortunately Flutter's video_player package doesn't support background video or audio playing. But you can use flutter_playout which wraps ExoPlayer on Android and AVPlayer framework on iOS with the ability to playback video in background or even lock screen. You can find out more about it here. Below is an example code provided by library's GitHub repo which plays a video and it keeps playing in background
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_playout/multiaudio/HLSManifestLanguage.dart';
import 'package:flutter_playout/multiaudio/MultiAudioSupport.dart';
import 'package:flutter_playout/player_observer.dart';
import 'package:flutter_playout/player_state.dart';
import 'package:flutter_playout/video.dart';
import 'package:flutter_playout_example/hls/getManifestLanguages.dart';
class VideoPlayout extends StatefulWidget {
final PlayerState desiredState;
final bool showPlayerControls;
const VideoPlayout({Key key, this.desiredState, this.showPlayerControls})
: super(key: key);
#override
_VideoPlayoutState createState() => _VideoPlayoutState();
}
class _VideoPlayoutState extends State<VideoPlayout>
with PlayerObserver, MultiAudioSupport {
final String _url = null;
List<HLSManifestLanguage> _hlsLanguages = List<HLSManifestLanguage>();
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, _getHLSManifestLanguages);
}
Future<void> _getHLSManifestLanguages() async {
if (!Platform.isIOS && _url != null && _url.isNotEmpty) {
_hlsLanguages = await getManifestLanguages(_url);
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
/* player */
AspectRatio(
aspectRatio: 16 / 9,
child: Video(
autoPlay: true,
showControls: widget.showPlayerControls,
title: "MTA International",
subtitle: "Reaching The Corners Of The Earth",
preferredAudioLanguage: "eng",
isLiveStream: true,
position: 0,
url: _url,
onViewCreated: _onViewCreated,
desiredState: widget.desiredState,
),
),
/* multi language menu */
_hlsLanguages.length < 2 && !Platform.isIOS
? Container()
: Container(
child: Row(
children: _hlsLanguages
.map((e) => MaterialButton(
child: Text(
e.name,
style: Theme.of(context)
.textTheme
.button
.copyWith(color: Colors.white),
),
onPressed: () {
setPreferredAudioLanguage(e.code);
},
))
.toList(),
),
),
],
),
);
}
void _onViewCreated(int viewId) {
listenForVideoPlayerEvents(viewId);
enableMultiAudioSupport(viewId);
}
#override
void onPlay() {
// TODO: implement onPlay
super.onPlay();
}
#override
void onPause() {
// TODO: implement onPause
super.onPause();
}
#override
void onComplete() {
// TODO: implement onComplete
super.onComplete();
}
#override
void onTime(int position) {
// TODO: implement onTime
super.onTime(position);
}
#override
void onSeek(int position, double offset) {
// TODO: implement onSeek
super.onSeek(position, offset);
}
#override
void onDuration(int duration) {
// TODO: implement onDuration
super.onDuration(duration);
}
#override
void onError(String error) {
// TODO: implement onError
super.onError(error);
}
}
As the video_player package now has the allowBackgroundPlayback option, I created this simple example showing how to integrate video_player and audio service.
example_video_player.dart
// This example demonstrates a simple video_player integration.
import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
// You might want to provide this using dependency injection rather than a
// global variable.
late AudioPlayerHandler _audioHandler;
Future<void> main() async {
_audioHandler = await AudioService.init(
builder: () => AudioPlayerHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Audio Service Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({Key? key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
late VideoPlayerController _controller;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.network('https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
videoPlayerOptions: VideoPlayerOptions(allowBackgroundPlayback: true))
..initialize().then((_) {
_audioHandler.setVideoFunctions(_controller.play, _controller.pause, _controller.seekTo, () {
_controller.seekTo(Duration.zero);
_controller.pause();
});
// So that our clients (the Flutter UI and the system notification) know
// what state to display, here we set up our audio handler to broadcast all
// playback state changes as they happen via playbackState...
_audioHandler.initializeStreamController(_controller);
_audioHandler.playbackState.addStream(_audioHandler.streamController.stream);
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
#override
void dispose() {
// Close the stream
_audioHandler.streamController.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Audio Service Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container(),
),
// Play/pause/stop buttons.
StreamBuilder<bool>(
stream: _audioHandler.playbackState.map((state) => state.playing).distinct(),
builder: (context, snapshot) {
final playing = snapshot.data ?? false;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_button(Icons.fast_rewind, _audioHandler.rewind),
if (playing) _button(Icons.pause, _audioHandler.pause) else _button(Icons.play_arrow, _audioHandler.play),
_button(Icons.stop, _audioHandler.stop),
_button(Icons.fast_forward, _audioHandler.fastForward),
],
);
},
),
// Display the processing state.
StreamBuilder<AudioProcessingState>(
stream: _audioHandler.playbackState.map((state) => state.processingState).distinct(),
builder: (context, snapshot) {
final processingState = snapshot.data ?? AudioProcessingState.idle;
return Text("Processing state: ${(processingState)}");
},
),
],
),
),
);
}
IconButton _button(IconData iconData, VoidCallback onPressed) => IconButton(
icon: Icon(iconData),
iconSize: 64.0,
onPressed: onPressed,
);
}
class MediaState {
final MediaItem? mediaItem;
final Duration position;
MediaState(this.mediaItem, this.position);
}
/// An [AudioHandler] for playing a single item.
class AudioPlayerHandler extends BaseAudioHandler with SeekHandler {
late StreamController<PlaybackState> streamController;
static final _item = MediaItem(
id: 'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3',
album: "Science Friday",
title: "A Salute To Head-Scratching Science",
artist: "Science Friday and WNYC Studios",
duration: const Duration(milliseconds: 5739820),
artUri: Uri.parse('https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg'),
);
Function? _videoPlay;
Function? _videoPause;
Function? _videoSeek;
Function? _videoStop;
void setVideoFunctions(Function play, Function pause, Function seek, Function stop) {
_videoPlay = play;
_videoPause = pause;
_videoSeek = seek;
_videoStop = stop;
mediaItem.add(_item);
}
/// Initialise our audio handler.
AudioPlayerHandler();
// In this simple example, we handle only 4 actions: play, pause, seek and
// stop. Any button press from the Flutter UI, notification, lock screen or
// headset will be routed through to these 4 methods so that you can handle
// your audio playback logic in one place.
#override
Future<void> play() async => _videoPlay!();
#override
Future<void> pause() async => _videoPause!();
#override
Future<void> seek(Duration position) async => _videoSeek!(position);
#override
Future<void> stop() async => _videoStop!();
void initializeStreamController(VideoPlayerController? videoPlayerController) {
bool _isPlaying() => videoPlayerController?.value.isPlaying ?? false;
AudioProcessingState _processingState() {
if (videoPlayerController == null) return AudioProcessingState.idle;
if (videoPlayerController.value.isInitialized) return AudioProcessingState.ready;
return AudioProcessingState.idle;
}
Duration _bufferedPosition() {
DurationRange? currentBufferedRange = videoPlayerController?.value.buffered.firstWhere((durationRange) {
Duration position = videoPlayerController.value.position;
bool isCurrentBufferedRange = durationRange.start < position && durationRange.end > position;
return isCurrentBufferedRange;
});
if (currentBufferedRange == null) return Duration.zero;
return currentBufferedRange.end;
}
void _addVideoEvent() {
streamController.add(PlaybackState(
controls: [
MediaControl.rewind,
if (_isPlaying()) MediaControl.pause else MediaControl.play,
MediaControl.stop,
MediaControl.fastForward,
],
systemActions: const {
MediaAction.seek,
MediaAction.seekForward,
MediaAction.seekBackward,
},
androidCompactActionIndices: const [0, 1, 3],
processingState: _processingState(),
playing: _isPlaying(),
updatePosition: videoPlayerController?.value.position ?? Duration.zero,
bufferedPosition: _bufferedPosition(),
speed: videoPlayerController?.value.playbackSpeed ?? 1.0,
));
}
void startStream() {
videoPlayerController?.addListener(_addVideoEvent);
}
void stopStream() {
videoPlayerController?.removeListener(_addVideoEvent);
streamController.close();
}
streamController = StreamController<PlaybackState>(onListen: startStream, onPause: stopStream, onResume: startStream, onCancel: stopStream);
}
}
I've been using the better_player package. It's quite good uses video_player and chewie and also has support for player notification and PiP.
And don't forget to enable the background audio capability on your xcode.
xcode-audio-capability
FLutter:
How to display video in video_player from the location of path_provider ?
you can copy paste run full code below
In demo, I use getApplicationDocumentsDirectory. you can print full path to check
make sure you have a file located in
/data/user/0/your_proejct_name/app_flutter/Movies/2019-11-08.mp4
code snippet
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
working demo
full code
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:video_player/video_player.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String dirPath;
bool loading = false;
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
void initState() {
// TODO: implement initState
load_path_video();
super.initState();
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
}
class VideoPlayPause extends StatefulWidget {
VideoPlayPause(this.controller);
final VideoPlayerController controller;
#override
State createState() {
return _VideoPlayPauseState();
}
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
_VideoPlayPauseState() {
listener = () {
setState(() {});
};
}
FadeAnimation imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
VoidCallback listener;
VideoPlayerController get controller => widget.controller;
#override
void initState() {
super.initState();
controller.addListener(listener);
controller.setVolume(1.0);
controller.play();
}
#override
void deactivate() {
controller.setVolume(0.0);
controller.removeListener(listener);
super.deactivate();
}
#override
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[
GestureDetector(
child: VideoPlayer(controller),
onTap: () {
if (!controller.value.initialized) {
return;
}
if (controller.value.isPlaying) {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.pause, size: 100.0));
controller.pause();
} else {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
controller.play();
}
},
),
Align(
alignment: Alignment.bottomCenter,
child: VideoProgressIndicator(
controller,
allowScrubbing: true,
),
),
Center(child: imageFadeAnim),
Center(
child: controller.value.isBuffering
? const CircularProgressIndicator()
: null),
];
return Stack(
fit: StackFit.passthrough,
children: children,
);
}
}
class FadeAnimation extends StatefulWidget {
FadeAnimation(
{this.child, this.duration = const Duration(milliseconds: 500)});
final Widget child;
final Duration duration;
#override
_FadeAnimationState createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation>
with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
super.initState();
animationController =
AnimationController(duration: widget.duration, vsync: this);
animationController.addListener(() {
if (mounted) {
setState(() {});
}
});
animationController.forward(from: 0.0);
}
#override
void deactivate() {
animationController.stop();
super.deactivate();
}
#override
void didUpdateWidget(FadeAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {
animationController.forward(from: 0.0);
}
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return animationController.isAnimating
? Opacity(
opacity: 1.0 - animationController.value,
child: widget.child,
)
: Container();
}
}
typedef Widget VideoWidgetBuilder(
BuildContext context, VideoPlayerController controller);
abstract class PlayerLifeCycle extends StatefulWidget {
PlayerLifeCycle(this.dataSource, this.childBuilder);
final VideoWidgetBuilder childBuilder;
final String dataSource;
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// a data source from the network.
class NetworkPlayerLifeCycle extends PlayerLifeCycle {
NetworkPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
#override
_NetworkPlayerLifeCycleState createState() => _NetworkPlayerLifeCycleState();
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// an asset as data source
class AssetPlayerLifeCycle extends PlayerLifeCycle {
AssetPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
#override
_AssetPlayerLifeCycleState createState() => _AssetPlayerLifeCycleState();
}
abstract class _PlayerLifeCycleState extends State<PlayerLifeCycle> {
VideoPlayerController controller;
#override
/// Subclasses should implement [createVideoPlayerController], which is used
/// by this method.
void initState() {
super.initState();
controller = createVideoPlayerController();
controller.addListener(() {
if (controller.value.hasError) {
print(controller.value.errorDescription);
}
});
controller.initialize();
controller.setLooping(true);
controller.play();
}
#override
void deactivate() {
super.deactivate();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.childBuilder(context, controller);
}
VideoPlayerController createVideoPlayerController();
}
class _NetworkPlayerLifeCycleState extends _PlayerLifeCycleState {
#override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.network(widget.dataSource);
}
}
class _AssetPlayerLifeCycleState extends _PlayerLifeCycleState {
#override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.asset(widget.dataSource);
}
}
/// A filler card to show the video in a list of scrolling contents.
Widget buildCard(String title) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.airline_seat_flat_angled),
title: Text(title),
),
// TODO(jackson): Remove when deprecation is on stable branch
// ignore: deprecated_member_use
ButtonTheme.bar(
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {
/* ... */
},
),
FlatButton(
child: const Text('SELL TICKETS'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
}
class VideoInListOfCards extends StatelessWidget {
VideoInListOfCards(this.controller);
final VideoPlayerController controller;
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
buildCard("Item a"),
buildCard("Item b"),
buildCard("Item c"),
buildCard("Item d"),
buildCard("Item e"),
buildCard("Item f"),
buildCard("Item g"),
Card(
child: Column(children: <Widget>[
Column(
children: <Widget>[
const ListTile(
leading: Icon(Icons.cake),
title: Text("Video video"),
),
Stack(
alignment: FractionalOffset.bottomRight +
const FractionalOffset(-0.1, -0.1),
children: <Widget>[
AspectRatioVideo(controller),
Image.asset('assets/flutter-mark-square-64.png'),
]),
],
),
])),
buildCard("Item h"),
buildCard("Item i"),
buildCard("Item j"),
buildCard("Item k"),
buildCard("Item l"),
],
);
}
}
class AspectRatioVideo extends StatefulWidget {
AspectRatioVideo(this.controller);
final VideoPlayerController controller;
#override
AspectRatioVideoState createState() => AspectRatioVideoState();
}
class AspectRatioVideoState extends State<AspectRatioVideo> {
VideoPlayerController get controller => widget.controller;
bool initialized = false;
VoidCallback listener;
#override
void initState() {
super.initState();
listener = () {
if (!mounted) {
return;
}
if (initialized != controller.value.initialized) {
initialized = controller.value.initialized;
setState(() {});
}
};
controller.addListener(listener);
}
#override
Widget build(BuildContext context) {
if (initialized) {
return Center(
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayPause(controller),
),
);
} else {
return Container();
}
}
}
I have searched the web for auto refresh widgets and auto refresh future JSON for Flutter but the results all seem to be for pull down refresh.
What I need is like a function that I can call and every minute that said function repeats.
I know I have to integrate something like:
var future = new Future.delayed(const Duration(milliseconds: 10), TracksWidget());
However I am not sure where I need to put it.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../model/track.dart';
class TracksWidget extends StatefulWidget {
#override
_TracksWidgetState createState() => _TracksWidgetState();
}
class _TracksWidgetState extends State<TracksWidget> {
Future<Track> track;
#override
Widget build(BuildContext context) {
double c_width = MediaQuery.of(context).size.width;
return new FutureBuilder<Track>(
future: track,
builder: (context, snapshot) {
if (snapshot.hasData) {
Track track = snapshot.data;
return new Container(
padding: const EdgeInsets.all(16.0),
width: c_width,
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(track.imageurl, width:200.0, height: 200.0,fit: BoxFit.cover),
Text(track.title),
Text(track.artist),
]),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
//By default, show a loading spinner.
return CircularProgressIndicator();
},
);
}
#override
void initState() {
super.initState();
track = fetchTrack();
}
Future<Track> fetchTrack() async {
final response =
await http.get('http://139.59.108.222:2199/rpc/drn1/streaminfo.get');
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
var responseJson = json.decode(response.body);
// assume there is only one track to display
// SO question mentioned 'display current track'
var track = responseJson['data']
.map((musicFileJson) => Track.fromJson(musicFileJson['track']))
.first;
return track;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
}
The simplest way to do this I have found is to use the Timer function.
If you put the timer into initState it will start when the the app is started.
In the code below, the timer will call the addValue() method every 5 seconds which increases the value by one each time. Just remember to dispose of the timer when you have finished with it.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer timer;
int counter = 0;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 5), (Timer t) => addValue());
}
void addValue() {
setState(() {
counter++;
});
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(counter.toString())
],
),
),
);
}
}