AdvStory
AdvStory Docs
Search
K
Comment on page

Creating Custom Contents

AdvStory exports StoryContent class which is also superclass of ImageContent and VideoContent. This means you can use any AdvStory method to create your own story content, just like predefined contents. AdvStory gives you all its power through StoryContent.
  • Extend StoryContent class, StoryContent is a StatefulWidget.
class MyCustomContent extends StoryContent {
...
@override
StoryContentState<MyStoryContent> createState() => MyStoryContentState();
}
  • For state class, extend StoyContentState.
class MyCustomContentState extends StoryContentState<MyCustomContent> {
...
}
StoryContentState provides methods for handling events, loading files, starting story and handling errors.
You have direct access following methods and variables inside of state class:

controller

Provided or default AdvStoryController.

loadingScreen

Provided or default loading screen that used in predefined contents.

position

Content and story position of this story content.

loadFile({required String url, Map<String, String>? requestHeaders, String? cacheKey})

Fetchs a file from the given url and caches it to local storage.

markReady({required Duration duration})

Marks the story content as ready to start. Call this method when content is ready to be display. When you call this method, if story views position is the position of this content, story flow starts immediately for the given duration, otherwise this content will be marked as ready and AdvStory will start the flow for this content when necessary.
If an AnimatedTray provided to AdvStory and the content is the first item in the story of tray, tray animation continues to play until this method is called.

shouldShowLoading

If the tray of this content is an AnimatedTray and this content is the first content in story, AdvStory builds content but keeps it off screen until you call markReady. Building an animated loading screen is completely unnecessary in this case. You can check this to not build a loading screen when the content is not ready.
This might increase opening performance significantly.
Widget build(BuildContext context) {
if(isMyResourcesLoaded) {
return buildMyContent();
}
return shouldShowLoading ? loadingScreen : const SizedBox();
}

isFirstContent

Returns true if this content is the first item in the tapped tray. This is different from 0th position.
For example, when user tap to second tray, if this contents position is StoryPosition(content: 0, story: 2) this method will return true, but if this contents position is StoryPosition(content: 0, story: 3) this method will return false.

setTimeout

Sets a timeout to call markReady. Use this method to set a time limit to take action when your content isn't ready at the requested time. Create your action in the onTimeout method.

Content Lifecycle Methods

initContent

Called when StoryContent class finished its initialization. You can call methods provided by StoryContentState inside of this method. This method is the first place you can use AdvStory methods and works only once. Execution order of state class methods is as follows:
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallbakc((_) {
print('postFrameCallback');
});
// Accessing AdvStory methods here throws exception
print('initState');
}
void didChangeDependencies() {
// Accessing AdvStory methods here throws exception
print('before didChangeDependencies.super()');
super.didChangeDependencies();
// AdvStory methods available here, but might work multiple times.
print('after didChangeDependencies.super()');
}
void initContent() {
print('initContent');
}
@override
Widget build(context) {
print('build');
...
}
This code will produce this output:
initState
before didChangeDependencies.super()
initContent
after didChangeDependencies.super()
build
postFrameCallback
Best place for fetching your resources and setting content is initContent and its recommended. But if you need to do it in another method, be sure didChangeDependencies.super() called.
@override
void initContent async () {
// Fetch and cache your video file
final videoFile = await loadFile(url: 'https://my.video.url');
// Create and initialize video player controller
videoController = VideoPlayerController.file(videoFile);
await videoController.initialize();
// Fetch and cache image file.
final imageFile = await loadFile(url: 'https://my.video.url');
_myImage = imageFile;
// Notify AdvStory when your content is ready to display.
markReady(const Duration(seconds: 10));
}

onStart

Called when content is on screen and should start. Start any process that needs to start in this method.
@override
void onStart() {
videoController.play();
}

onPause

Called when story is paused. Pause any ongoing process in this method.
This method works on every tap. AdvStorycalls this method very often, don't do expensive operations to avoid performance issues.
@override
void onPause() {
videoController.pause();
}

onResume

Called when story is resumed. Resume any paused process in this method.
This method works on every tap up except of story/content skip. AdvStorycalls this method very often, don't do expensive operations to avoid performance issues.
@override
void onResume() {
videoController.play();
}

onStop

Called when content is not visible on screen and should stop. Stop any ongoing process and reset its progress in this method, but don't dispose anything.
@override
void onStop() {
videoController.stop();
videoController.seekTo(Duration.zero);
}

onTimeout

Called when the end of the set timeout is reached. Use this method to handling content preparing errors.
@override
void onTimeout() {
setState(() {
_activeView = Center(
child: Text('Error loading story!'),
);
});
Future.delayed(const Duration(seconds: 2), () => controller.toNextContent());
}

Example

class MyCustomContent extends StoryContent {
const MyCustomContent({Key? key}) : super(key: key);
@override
StoryContentState<MyCustomContent> createState() => MyCustomStoryContentState();
}
class MyCustomContentState extends StoryContentState<MyCustomContent> {
VideoPlayerController? _videoController;
bool _hasError = false;
@override
void initContent() async {
// Set a timeout to make this content ready.
setTimeout(const Duration(seconds: 3));
// Fetch file and initialize video player
final videoFile = await loadFile(url: 'my.video.url');
_videoController = VideoPlayerController.file(videoFile);
await _videoController.initialize();
// Notify AdvStory
markReady(duration: _videoController.value.duration);
}
@override
Widget build(BuildContext context) {
// Check if timeout has reached to the end, show an error
// message.
if(_hasError) return Text('Error');
if(_videoController?.value.isInitialized) {
return Center(
child: AspectRatio(
aspectRatio: _videoController!.value.aspectRatio,
child: VideoPlayer(_videoController!),
),
);
}
// Check if showing a loading screen is necessary for this content.
return shouldShowLoading ? loadingScreen : const SizedBox();
}
@override
void onStart() {
_videoController?.play();
}
@override
onResume() {
_videoController?.play();
}
@override
void onPause() {
_videoController?.pause();
}
@override
void onStop() {
_videoController?.pause();
_videoController?.seekTo(Duration.zero);
}
@override
void onTimeout() {
setState(() {
_hasError = true;
});
}
@override
void dispose() {
_videoController?.dispose();
super.dispose();
}
}