From Loading Screens to Lightning Fast: Harnessing the Power of Preloading in Flutter

Yash Mittal
4 min readMar 15, 2023

--

Consider when you have to display a list of images or videos in the application. For faster loading, you will cache the images or videos in device local storage, but this will increase the initial load time and also occupy local storage. What if we could preload these videos or images without using local storage?

Preloading refers to loading and caching assets or resources before they are actually required by the application. This is done to reduce the loading time of the app and improve the user experience.

Today we are going to use the preloading concept when loading videos using the video_player package in Flutter.

Setup

Install the following dependency for playing videos in the application:

Video_player

Logic

Consider a list of videoUrls:

var listOfUrls=[
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
];

To preload video you need to create list of video controllers :

class preloader{

final videoPlayerController = <VideoPlayerController>[];

for (var i = 0; i < listOfUrls.length; i++) {
videoPlayerController.add(VideoPlayerController.network(
listOfUrls[i],
));
}

Now you have the list of video controllers that are ready to display video on the device. To preload video, when you are playing video of index i=0, in the same interval of time you need to initialize i+1 videoController.

Steps:

if index =0:
initialise index 0 and 1

else if index >0:
stop index-1;
dispose index-2 ;
play index;
and initalize index+1;
  1. Initialize videoController [0], playing videoController [1], and initialize videoController [2],
  2. Dispose videoController[0], initialize videoController[1], play videoController[2] and initialize videoController[3].
  void _playNextReel(int index) {
/// Stop [index - 1] controller
1) _stopControllerAtIndex(index - 1);

/// Dispose [index - 2,3] controller
2) _disposeControllerAtIndex(index - 2);

/// Play current video (already initialized)
3) _playControllerAtIndex(index);

/// Initialize [index + 1] controller
4) _initializeControllerAtIndex(index + 1);
}

The function PlayNextReel() will STOP the previous video controller, DISPOSE index-2 controller (to prevent memory leaks and app crashes), and then PLAY the current index. Also, during playback, it will INTIALIZE the index+1 video controller so that when the user scrolls down within a fraction of a second, he will see the video.

  void _playPreviousReel(int index) {
/// Stop [index + 1] controller
1) _stopControllerAtIndex(index + 1);

/// Dispose [index + 2] controller
2) _disposeControllerAtIndex(index + 2);

/// Play current video (already initialized)
3) _playControllerAtIndex(index);

/// Initialize [index - 1] controller
4) _initializeControllerAtIndex(index - 1);

The same concept applies in playing previous reels, but make sure to dispose of them to prevent the app from crashing. Now let’s define these functions.


void _stopControllerAtIndex(int index) {
if (listOfUrls.length > index && index >= 0) {
/// Get controller at [index]
final _controller = videoPlayerController[index];

/// Pause
_controller.pause();

/// Reset postiton to beginning
_controller.seekTo(const Duration());
logger.w('stopped $index');
}
}

void _disposeControllerAtIndex(int index) {
if (listOfUrls.length > index && index >= 0) {
/// Get controller at [index]
final _controller = videoPlayerController[index];

/// Dispose controller
_controller.dispose();
logger.w('Disposed $index');
}
}

void _playControllerAtIndex(int index) {
if (listOfUrls.length > index && index >= 0) {
/// Get controller at [index]
final _controller = videoPlayerController[index];
if (_controller.value.isInitialized) {
/// Play controller
_controller.play();

_controller.setLooping(true);
} else {
_initializeControllerAtIndex(index).then((value) {
videoPlayerController[index].play();
videoPlayerController[index].setLooping(true);
setState(() {});
});
}
logger.w('playing $index');
}
}

Future _initializeControllerAtIndex(int index) async {
if (listOfUrls.length > index && index >= 0) {
/// Create new controller
final _controller = VideoPlayerController.network(
reelsData?.data?.shorts?[index].url ?? '');

/// Add to [controllers] list
videoPlayerController[index] = _controller;

/// Initialize
await _controller.initialize().then((value) {
setState(() {});
});

logger.w('initialized $index');
}
}

Important

If you have used Instagram and have seen the Instagram reels section, you must have noticed that whenever you scroll quickly, Instagram will let you scroll for 3–4 videos only, and after that, some gray skeleton loading is shown. This is done to prevent memory leaks and app crashes. To add this functionality, you need to add a debouncer and disable physics if video is not initialized. Here is the code.

onIndexChanged: (index) {
_isPlaying = true;
currentIndex = index;
if (index > focusedIndex) {
Debouncer.debounce(
'debounce', const Duration(milliseconds: 200),
() async {
_playNextReel(index);
});
} else {
Debouncer.debounce(
'debounce', const Duration(milliseconds: 200),
() async {
_playPreviousReel(index);
});
}
focusedIndex = index;

Here is the complete code of build method :

 Swiper(
physics: videoPlayerController[currentIndex]
.value
.isInitialized
? const AlwaysScrollableScrollPhysics()
: const NeverScrollableScrollPhysics(),
loop: false,
scrollDirection: Axis.vertical,
itemCount: videoPlayerController.length,
onIndexChanged: (index) {
_isPlaying = true;
currentIndex = index;
if (index > focusedIndex) {
Debouncer.debounce(
'debounce', const Duration(milliseconds: 200),
() async {
_playNextReel(index);
});
} else {
Debouncer.debounce(
'debounce', const Duration(milliseconds: 200),
() async {
_playPreviousReel(index);
});
}
focusedIndex = index;
},
itemBuilder: (context, index) {
return focusedIndex == index
? videoPlayerController[index].value.isInitialized
? FadeIn(
duration:
const Duration(milliseconds: 900),
child: GestureDetector(
onTap: () {
if (_isPlaying) {
videoPlayerController[index]
.pause();
_pause();
} else {
videoPlayerController[index].play();
videoPlayerController[index]
.setLooping(true);

_play();
}
},
child: ClipRRect(
borderRadius:
BorderRadius.circular(6),
child: Container(
child: videoPlayerController[index]
.value
.isInitialized
? AspectRatio(
aspectRatio:
videoPlayerController[
index]
.value
.aspectRatio,
child: VideoPlayer(
videoPlayerController[
index]),
)
: Container(),
),
),
),
)
: const Center(
child: InsaCircularProgressIndicator())
: const Center(
child: InsaCircularProgressIndicator());
},
);

Preloading not only improves the performance of the app but also provides a smoother and more seamless experience for users. It is especially useful for apps that deal with high-quality or long-duration videos.

Overall, preloading videos using Flutter can be an effective way to enhance the user experience of your application and ensure that your users have a positive experience with your app.

We have successfully preloaded videos, feel free to add comments if you face any issues implementing this concept.

Happy coding 😄

--

--

Yash Mittal
Yash Mittal

Written by Yash Mittal

Frustrated coder, cooking code, burning hands and repeating recursively until a delicious dish pop out of my kitchen to be served as innovation.

Responses (1)