Introducing flutter admin kit

24. August 2021

Introducing flutter admin kit

View code on Github.

What is flutter admin kit?

The flutter admin kit combines the fine art of an interactive web frontend with a powerful data abstraction layer. The goal is to provide a kit, also called a starter template, that reduces the boring boilerplate necessary to get the basics of authentication and CRUD functionality on your models done. At the same time you should be able to use the component based approach of Flutter to create and plug your complex UI elements wherever you want them, without starting over dealing with list paging/scrolling, URL routes setup, low-level form handling, save-points when editing, error handling and all that stuff we just expect from a modern web interface.

This is achieved by combining state-of-the-art libraries that give strong (and strict) guidelines that will keep the admin interface cleanly organized and modular in certain concerns. For example you should be able to jump anywhere by a link, not breaking/loosing your state. If a user has started a user journey that must be completed he should be informed about effects of switching to another view.

Technically these are the highlights implemented so far:

  • Declarative routing via global state, organized with flutter_riverpod

  • Model list view dynamically generated from configuration files and an inspection API

  • Authentication with OAuth based on auto-refreshed JWTs

  • Debug panel for live inspection of global state for a nice learning experience :)

State management

In this video I'm going through the list of used packages and explain why they are useful. You can find the up-to-date list in pubspec.yaml.

Then we look at the most opinionated decision of flutter-admin-kit: how most state is organized globally and interconnected in a composable pattern:

final titleProvider = StateProvider<String>((ref) {
  final mainView = ref.watch(mainViewProvider);
  return mainView.state?.map(
        resolved: (view) => 'View Not loaded',
        loaded: (view) => view.view.title,
        notFound: (view) => 'Not found',
      ) ??
      '';
});

I try to support the mental model by visualizing state providers and their dependencies as a graph.

(you can fast-forward to specific chapters marked with (code) and (diagram) via the index)

Declarative routing by global state

The flutter-admin-kit supports advanced routing with the updated navigation and routing mechanism (2.0). This mechanism is different to the imperative push/pop-manner and introduces declarative routing. The declarative approach is a better fit for globally defined navigation state. The main switch is made by using the MaterialApp.router factory constructor:

return MaterialApp.router(
        routerDelegate: delegate,        routeInformationParser: _routeInformationParser,        // ...
    );

The declarativity is hidden in delegate. The delegate is our custom subclass of RouterDelegate<RoutedView> and has two parts:

  • It's a widget by itself, hence defines a build method, and this builds the current page. Each page is a MaterialPage widget that Flutter knows how to layer up and animate on route changes.
Widget build(BuildContext context, WidgetRef ref) {
    // watch the current viewstack and trigger rebuilds when it updates
    final viewStack = ref.watch(viewStackProvider);
    // map views on page stack to actual flutter widgets representing pages
    final pages = viewStack.map((view) => _mapViewToPage(context, view, ref)).toList();

    return Navigator(
        key: navigatorKey,
        pages: pages,        onPopPage: (route, dynamic result) {
            // ...
        },
    );
}
  • It wires up the Flutter navigation machinery with our custom handling of navigation state (with riverpod's global state management). This includes settings up notification in both directions
    • upon changes in our global state hierarchy that have an effect on navigation
    • and incoming route changes passed on to us by Flutter.

Future<void> setNewRoutePath(RoutedView configuration) async {}

RoutedView? get currentConfiguration {}

Future<bool> popRoute() async {}

void addListener(VoidCallback listener) {}


void removeListener(VoidCallback listener) {}

(you can fast-forward to specific chapters marked with (code) and (diagram) via the index)

Token Authentication

Modern authentication schemes can be confusing, the important bits get easily burried in protocol details or the math behind protocols. While the details matter a lot in security I tried to hit the right degree when explaining. We will use a slightly modified version of OAuth 2.0 Authorization Code flow with the PKCE Extension.

(Video coming soon)

I keep you updated

To get updated about this and related educational projects, just get in touch by email to simon@smartnuance.com or like the Github repo.

(Why no fancy newsletter subscription? - Mainly because I care about safety of my (and your!) data, I'm currently working on my own saas-kit based on Golang, soon to support simple but perfect multi-channel newsletters :D )