I am building a videoplayer application in Flutter that plays multiple videos one by one by fetching from the database
After completion of one video we need to transfer videocontroller to another video so first I dispose video controller and initialize with another video
It works fine for 4,5 video sequence and then throw a error like this
throw FlutterError(
'A $runtimeType was used after being disposed.\n'
'Once you have called dispose() on a $runtimeType, it can no longer be used.',
);
Part of my code throwing this error is
void playVideo() async {
if (DateTime.now().isAfter(videoData[index].start_from) &&
DateTime.now().isBefore(videoData[index].end_on)) {
bool played_count = await CountQuery().whenComplete(() {
});
file = File(videoData[index].file_link);
if (file.existsSync() && !played_count) {
isEnd = true;
controller = VideoPlayerController.file(file)
..initialize().then((value) {
setState(() {
controller.play();
});
controller.addListener(() {
// checking the duration and position every time
// Video Completed//
if (controller.value.duration == controller.value.position &&
isEnd) {
setState(() {
isEnd = false;
});
CreateLog("video");
controller.dispose();
PlayNext();
}
});
})
..setLooping(false);
} else {
PlayNext();
}
} else {
PlayNext();
}
}
void PlayNext() {
setState(() {
index++;
if (videoData.length > index) {
// this.asset = videoData[index].file_link;
this.file = File(videoData[index].file_link);
playVideo();
} else {
index = 0;
// this.asset = videoData[index].file_link;
this.file = File(videoData[index].file_link);
playVideo();
}
});
}
Note: I am using video_player: ^2.4.2
I solve this problem by adding controller = null after each dispose
Related
I have a StateNotifierProvider that calls an async function which loads some images from the internal storage and adds them to the AsyncValue data:
//Provider declaration
final paginationImagesProvider = StateNotifierProvider.autoDispose<PaginationNotifier, AsyncValue<List<Uint8List?>>>((ref) {
return PaginationNotifier(folderId: ref.watch(localStorageSelectedFolderProvider), itemsPerBatch: 100, ref: ref);
});
//Actual class with AsyncValue as State
class PaginationNotifier extends StateNotifier<AsyncValue<List<Uint8List?>>> {
final int itemsPerBatch;
final String folderId;
final Ref ref;
int _numberOfItemsInFolder = 0;
bool _alreadyFetching = false;
bool _hasMoreItems = true;
PaginationNotifier({required this.itemsPerBatch, required this.folderId, required this.ref}) : super(const AsyncValue.loading()) {
log("PaginationNotifier created with folderId: $folderId, itemsPerBatch: $itemsPerBatch");
init();
}
final List<Uint8List?> _items = [];
void init() {
if (_items.isEmpty) {
log("fetchingFirstBatch");
_fetchFirstBatch();
}
}
Future<List<Uint8List?>> _fetchNextItems() async {
List<AssetEntity> images = (await (await PhotoManager.getAssetPathList())
.firstWhere((element) => element.id == folderId)
.getAssetListRange(start: _items.length, end: _items.length + itemsPerBatch));
List<Uint8List?> newItems = [];
for (AssetEntity image in images) {
newItems.add(await image.thumbnailData);
}
return newItems;
}
void _updateData(List<Uint8List?> result) {
if (result.isEmpty) {
state = AsyncValue.data(_items);
} else {
state = AsyncValue.data(_items..addAll(result));
}
_hasMoreItems = _numberOfItemsInFolder > _items.length;
}
Future<void> _fetchFirstBatch() async {
try {
_numberOfItemsInFolder = await (await PhotoManager.getAssetPathList()).firstWhere((element) => element.id == folderId).assetCountAsync;
state = const AsyncValue.loading();
final List<Uint8List?> result = await _fetchNextItems();
_updateData(result);
} catch (e, stk) {
state = AsyncValue.error(e, stk);
}
}
Future<void> fetchNextBatch() async {
if (_alreadyFetching || !_hasMoreItems) return;
_alreadyFetching = true;
log("data updated");
state = AsyncValue.data(_items);
try {
final result = await _fetchNextItems();
_updateData(result);
} catch (e, stk) {
state = AsyncValue.error(e, stk);
log("error catched");
}
_alreadyFetching = false;
}
}
Then I use a scroll controller attached to a CustomScrollView in order to call fetchNextBatch() when the scroll position changes:
#override
void initState() {
if (!controller.hasListeners && !controller.hasClients) {
log("listener added");
controller.addListener(() {
double maxScroll = controller.position.maxScrollExtent;
double position = controller.position.pixels;
if ((position > maxScroll * 0.2 || position == 0) && ref.read(paginationImagesProvider.notifier).mounted) {
ref.read(paginationImagesProvider.notifier).fetchNextBatch();
}
});
}
super.initState();
}
The problem is that when the StateNotifierProvider is fetching more data in the async function fetchNextBatch() and I go back on the navigator (like navigator.pop()), Flutter gives me an error:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Tried to use PaginationNotifier after dispose was called.
Consider checking mounted.
I think that the async function responsible of loading data completes after I've popped the page from the Stack (which triggers a Provider dispose).
I'm probably missing something and I still haven't found a fix for this error, any help is appreciated.
I want to release everything when leaving the current screen.
Getx advising me to use onClose method of GetxController for this.
#override
void onClose() {
pagingController.dispose();
super.onClose();
}
He kept his promise, so nice. But it didn't take long! Let's dive in;
The controller has a future method called _fetchPage, it basically fetches page data and works with pagingController
If I leave the screen before completing the _fetchPage, the future remains working. Once the data is fetched, pagingController is accessed but it has been already disposed.
And finally, it prints Unhandled Exception: Exception: A PagingController was used after being disposed.
Future<void> _fetchPage(int pageKey) async {
try {
var skip = pageKey == 0 ? 0 : (10 * pageKey);
var data = await fetchDataOfPage(skip, limit);
final isLastPage = data.length < limit;
if (isLastPage) {
pagingController.appendLastPage(data);
} else {
final nextPageKey = pageKey + 1;
pagingController.appendPage(data, nextPageKey);
}
} catch (error) {
pagingController.error = error;
}
}
I had this exact problem and was able to fix it by checking that the widget is still mounted after retrieving the data:
Future<void> _fetchPage(int pageKey) async {
try {
var skip = pageKey == 0 ? 0 : (10 * pageKey);
var data = await fetchDataOfPage(skip, limit);
// bail out if widget is no longer mounted
if (!mounted) {
return;
}
final isLastPage = data.length < limit;
if (isLastPage) {
pagingController.appendLastPage(data);
} else {
final nextPageKey = pageKey + 1;
pagingController.appendPage(data, nextPageKey);
}
} catch (error) {
pagingController.error = error;
}
}
I'm trying to make some kind of an mp3 player app and i'm using audioplayers package. And it's been working fine on Android, but on IOS the onDurationChanged doesn't seem to be getting called.
And since i'm also showing a slider, it gives an Error on IOS because the max value returns null
Here's my code
class AudioProvider extends ChangeNotifier {
AudioProvider() {
initAudio();
}
AudioPlayer _audioPlayer = AudioPlayer();
Duration totalDuration;
Duration position;
String audioState;
initAudio() {
_audioPlayer.onDurationChanged.listen((updatedDuration) {
totalDuration = updatedDuration; // This doesn't work on IOS, totalDuration == null
notifyListeners();
});
_audioPlayer.onAudioPositionChanged.listen((updatedPosition) {
position = updatedPosition;
notifyListeners();
});
_audioPlayer.onPlayerStateChanged.listen((playerState) {
if (playerState == AudioPlayerState.STOPPED) audioState = "Stopped";
if (playerState == AudioPlayerState.PLAYING) audioState = "Playing";
if (playerState == AudioPlayerState.PAUSED) audioState = "Paused";
notifyListeners();
});
}
playPauseAudio(String url, bool alreadyPlaying) async {
if (!alreadyPlaying) {
position = null;
totalDuration = null;
await _audioPlayer.play(url);
notifyListeners();
}
if (audioState == 'Playing') {
await _audioPlayer.pause();
} else {
await _audioPlayer.resume();
}
notifyListeners();
}
void stop() {
_audioPlayer.stop();
}
void seekToSec(Duration durationToSeek) {
_audioPlayer.seek(durationToSeek);
notifyListeners();
}
I have implemented a pagination scheme in my flutter app but im not sure if its performance friendly and if it may result to trouble on production in future so i would like to get advice on it.
here is my implementation
First, i get data using a stream provider in my parent widget.
class BuyerSellerPostsPage extends StatefulWidget {
#override
_BuyerSellerPostsPageState createState() => _BuyerSellerPostsPageState();
}
class _BuyerSellerPostsPageState extends State<BuyerSellerPostsPage> {
...some code here...
bool isAtBottom = false;
int postToDisplay=10;
#override
void initState() {
super.initState();
// Setup the listener.
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
setState(() {
isAtBottom=true;
postToDisplay+=10;
print('now true');
});
}else{
setState(() {
print('now false');
isAtBottom=false;
});
}
}
});
}
#override
void dispose(){
_scrollController.dispose();
super.dispose();
}
...some code here..
#override
Widget build(BuildContext context) {
Position coordinates=Provider.of<Position>(context);
...some code here...
body: SingleChildScrollView(
controller: _scrollController,
child: StreamProvider<List<SellerPost>>.value(
value: SellerDatabaseService(
currentLocation: new GeoPoint(coordinates.latitude, coordinates.longitude,)
,filters: _setFilters,
selectedCategory: _selectedCategory,
selectedTag: _selectedTag,
postsToDisplay: postToDisplay
).inRangeSellerPosts ,
...some code here...
);
}else{
return Container(
);
}
},
),
),
//post list
BSellerPostList(),
...some code here...
}
}
The initial posts to display are 10.
In my initstate i have used a listener to my scroll controller so that when the user scrolls to the bottom more items(+10) are loaded on screen.
In my stream provider i pass the postsToDisplay int to my stream in the backend below
Stream <List<SellerPost>> get inRangeSellerPosts {
try {
return sellerPostCollection
.where("expireTime" , isGreaterThan: DateTime.now())
.orderBy('expireTime',descending: true)
.snapshots()
.map(yieldSellerPosts);
} catch (e) {
print(e.toString());
return null;
}
}
List<SellerPost> yieldSellerPosts(QuerySnapshot snapshot) {
List<String> l = [];
print(snapshot.documents.length);
try {
return snapshot.documents.map((doc) {
return SellerPost(
...some code here...
);
}).take(postsToDisplay)
.toList();
} catch (e) {
print(e.toString());
return null;
}
}
I now get the snapshots and use the take method on the list to get only the required number(postsToDisplay).
This method works fine in my debug mode. Im not sure how it will behave in production or with large data sets. Could someone scrutinise it, i would appreciate alot.
I personally used a modified version of this answer posted in a previous similar question.
Both my implementation and that other guys implementation have the pros and cons.
His method leads to listening to document changes for documents you may never need to use considering you may need only 10 paginated items. My method on the other hand does not work along with the stream. It uses a future to query for document snapshots to update the list as you proceed.
Here is the sample code
bool _isRequesting = false;
bool _isFinish = false;
final _scrollController = ScrollController();
List<DocumentSnapshot> _posts = [];
#override
void initState() {
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
setState(() {
requestNextPage();
});
}
}
});
requestNextPage();
super.initState();
}
void requestNextPage() async {
try{
if (!_isRequesting && !_isFinish) {
QuerySnapshot querySnapshot;
_isRequesting = true;
if (_posts.isEmpty) {
//check if _posts list is empty so that we may render the first list of 10 items.
querySnapshot = await Firestore.instance
.collection('sellerPost')
.limit(10)
.getDocuments();
} else {
//if _posts list is not empty it means we have already rendered the first 10 items so we start querying from where we left off to avoid repetition.
querySnapshot = await Firestore.instance
.collection('sellerPost')
.startAfterDocument(_posts[_posts.length - 1])
.limit(10)
.getDocuments();
}
if (querySnapshot != null) {
int oldSize = _posts.length;
_posts.addAll(querySnapshot.documents);
int newSize = _posts.length;
if (oldSize == newSize) {
_isFinish = true;
}
_isRequesting = false;
}else{
_isFinish = false;
_isRequesting = false;
}
}catch(e){
print(e.toString());
}
}
So in this above code i used the scroll controller to detect when the user scrolls to the bottom of the page with the paginated items e.g 10 posts. This event triggers my function requestNextPage(); Note that on inititState we also call the requestNextPage(); to render the initial 10 posts.
So now, the each time a scroll to bottom is detected, 10 extra posts are added to
_posts
I have uploaded some mp3 files to my google drive. And now I want to play those mp3 files in my flutter app using audioplayers package.
https://drive.google.com/file/d/1v8RBvEOpsEDlD_o7OA2TKgxysF-O5jUW/view?usp=sharing. This is my public shareable link of my mp3 on google drive.
void getAudio() async {
var url =
"https://drive.google.com/file/d/1v8RBvEOpsEDlD_o7OA2TKgxysF-O5jUW/view?usp=sharing";
if (isPlaying) {
var res = await audioPlayer.pause();
if (res == 1) {
print("isPlaying $res");
setState(() {
isPlaying = false;
});
}
} else {
var res = await audioPlayer.play(url, isLocal: false);
print("!isPlaying $res");
if (res == 1) {
setState(() {
isPlaying = true;
});
}
}
audioPlayer.onDurationChanged.listen((Duration d) {
setState(() {
duration = d;
});
});
audioPlayer.onAudioPositionChanged.listen((Duration d) {
setState(() {
position = d;
});
});
}
you have to try direct url for your file like this
https://drive.google.com/uc?export=download&confirm=no_antivirus&id=1v8RBvEOpsEDlD_o7OA2TKgxysF-O5jUW