Flutter for Single-Page Scrollable Websites with Navigator 2.0 — Part 4: Ensure Visible

In the second and third part of this series, we explored implementing a single-page scrollable website (SPSW) whose sections are built lazily and have equal height. In this article, we will build an SPSW with sections that have varying and unpredictable heights before being laid out. Instead of building the sections lazily, all the sections will be built at once within a SingleChildScrollView widget.

If you haven’t read the previous articles, I would strongly suggest starting from the first part of this series since the implementation details of the common widgets will not be mentioned in this part.

A common example of this SPSW type is docsify which is a documentation site generator. Famous Flutter libraries such as Bloc, and Hive use this tool for documentation.

HomeScreen

In the first and second sample apps, the HomeScreen had a Column which included TopNavigationMenu, and ColorsSection as its children. In this sample app, the HomeScreen is a Row with a SideNavigationMenu, and the ColorsSection .

SideNavigationMenu

SideNavigationMenu is very similar to the TopNavigationMenu widget except that it lists all the navigation menu buttons on the vertical axis. Since the NavigationMenuButton we use in this sample app is the same as in the previous sample, we will not repeat the implementation details in this article.

ColorsSection

ColorsSection is a scrollable widget which lists all the sections that have varying sizes. In order to be able to scroll to a section programmatically, we need the size information of the sections that are listed before the destination section in the scroll direction. We can only get the size information when the sections are laid out. Hence, in this sample, we won’t be building the sections lazily.

Similar to the previous sample, ColorsSection :

  • listens to the color code values coming from different sources and scrolls to the corresponding color section programmatically if the source of the update is not scrolling.
  • notifies the color code listeners when the first visible section (trailingIndex ) changes or a shaped button is clicked.

Unlike the previous samples, in this sample app, in addition to the shaped buttons, every section has a title and 2 to 6 paragraphs containing up to 900 words of Lorem ipsum placeholder text.

Lorem ipsum text is randomly generated using the flutter_lorem package. A unique seed is used for each color section to provide the same number of paragraphs and words on each build.

Listen to Color Code Update

In the initState of the ColorsSection widget we do the following:

  • Prepare the GlobalKey list to be used for the color sections.
  • Scroll to the initial position based on the value in the colorCodeNotifier . We can’t scroll to a position in the initState method because the widget is not laid out when it is called. Thus, we will scroll in the next frame after the first build using the addPostFrameCallback . This will cause a scroll effect each time the webpage is launched by entering the URL on the address bar.
  • Subscribe to the colorCode updates and programmatically scroll to the target section if the source of the update is not scrolling.

In a SingleChildScrollView widget, we can programmatically scroll to a destination section using the static Scrollable.ensureVisible method.

ensureVisible: Scrolls the scrollables that enclose the given context so as to make the given context visible.”

We will get the GlobalKey of the destination section widget and provide the currentContext of the GlobalKey as a parameter to Scrollable.ensureVisible method and scroll programmatically with animation.

Notify a Color Code Update

We want to notify the color code listeners according to the first visible section (trailingIndex ) changes as the user scrolls.

We provide all the sections to a Column widget as its children and add the Column widget as a direct child of a SingleChildScrollView. Using NotificationListener widget, we first filter UserScrollNotification type of notifications as the user scrolls. Then, we will get the current offset from the notification to find the trailing index (first visible index).

To determine the trailing index when a scroll notification is received, we iteratively compare the cumulative height of the sections with the current offset. Starting from the first section, we add up each section’s height in a for-loop. The trailing index will be the first index where the cumulative height is larger than the offset of the section.

When we assigned a GlobalKey to each section. The currentContext property of the GlobalKey provides the BuildContext of the section. Using the size property of BuildContext we can get the height of each section.

Here is the full code for the ColorSections widget:

Resizing the Browser Window

As of 2.2.0 stable version, Flutter framework doesn’t rebuild widgets when the browser window is resized due to performance optimization. In this sample app, when the browser window is resized, the app shows the wrong section, however, it gets fixed on the next user scroll events or button clicks. In my opinion, this is not good but ok behavior from a user experience point and does not require forcing build to correct pixels after window resize.

Conclusion

In this article, we learned how to live with building all the sections at once in a SingleChildScrollView + Column . This is clearly the most undesired and expensive way of implementing scroll logic for single-page scrollable websites. However, if the number of sections is small, and the content of each section is not expensive this can still be a good option due to its simplicity. In the next article, we will see lazily building a scrollable widget with varying-sized sections like an illusion.

You can run this sample app by right-clicking on the main_003.03.dart file in the project source code. If you liked this article, please press the clap button, and star the Github repository of the sample apps.

Flutter and Android Mobile App Developer