Skip to content

Using Plugins

Plugins as discussed in the Features and Plugins article, is all about cross-cutting functionality that is available to all features. Think of it as the Frontend brain for the User-facing feature. The classic example is the Authentication plugin. It allows you to invoke auth operations like login, logout without worrying about the UI. The authentication feature provides the UI and invokes the auth plugin (via vyuh.auth) as per the User events.

This separation of Plugins into their own space allows features to focus more on the presentation and less on the plumbing used to achieve the user-facing functionality.

Setting Up Plugins

The Vyuh framework has built in plugins for most of the common scenarios. This helps you to get started quickly without configuring all of them at the beginning. However, as you start building more complex apps, it would be required to setup custom plugins and integrations that are suited for your app.

This is done as part of the runApp() call, from the vyuh_core package. Here is an example of the invocation with the various plugins.

lib/main.dart
void main() async {
final plugins = await _getPlugins();
vc.runApp(
initialLocation: '/demo',
features: () => [
system.feature,
news.feature,
developer.feature,
tmdb.feature,
food.feature,
onboarding.feature,
wonderous.feature,
firebase_auth.feature(),
misc.feature,
forms.feature,
],
plugins: plugins,
),
);
}
_getPlugins() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// Ensure all imperatively navigated URLs are shown in the URL bar
DefaultNavigationPlugin.enableURLReflectsImperativeAPIs();
DefaultNavigationPlugin.usePathStrategy();
return PluginDescriptor(
content: DefaultContentPlugin(
provider: SanityContentProvider.withConfig(
config: SanityConfig(
projectId: '<project-id>',
dataset: 'production',
perspective: Perspective.previewDrafts,
useCdn: false,
token: '<token>',
),
cacheDuration: const Duration(seconds: 5),
),
),
analytics: vc.AnalyticsPlugin(
providers: [
FirebaseAnalyticsProvider(),
],
),
telemetry: vc.TelemetryPlugin(
providers: [
vc.ConsoleLoggerTelemetryProvider(),
SentryTelemetryProvider(
config: SentryProviderConfig(
sampleRate: 0.2,
dsn: '<dsn>',
),
),
],
),
auth: FirebaseAuthPlugin(),
others: [
FirebaseFeatureFlagPlugin(
settings: RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: const Duration(seconds: 10),
),
),
],
);
}

In the above code-block, we are using a variety of custom configured plugins:

  • Content: configured with the Sanity provider
  • Navigation: using the GoRouter package
  • Analytics: uses Firebase Analytics, Performance and Crashlytics
  • Feature Flag: using Firebase Remote Config
  • Logger: the built-in console logger
  • Authentication: using Firebase Auth

Available Plugins

There are many built-in plugins available in the Open Source plan and many more are being added for the Enterprise plans. These plugins fall into the following categories:

CategoryAPI
Contentvyuh.content
Networkvyuh.network
Authenticationvyuh.auth
Navigationvyuh.router
Dependency Injectionvyuh.di
Environmentvyuh.env
Analyticsvyuh.analytics
Telemetryvyuh.telemetry
Feature Flagvyuh.featureFlag
Loggervyuh.log
Eventsvyuh.event

We also plan to introduce new categories in the future that caters to simplifying various aspects of the Application. An example could be User Feedback.

Using the ContentPlugin

The ContentPlugin is one of the core plugins in the Vyuh Framework, accessible via the vyuh.content API. It allows you to interact with the CMS content and render it on the Flutter widget.

It also takes care of registering various types that are found during the deserialization process of reading the JSON content from CMS. This helps in ensuring that you have a type-safe Dart object when working inside your Flutter code. Some of the core responsibilities of the Content plugin include:

Content Building

Content can be rendered with the buildContent() and buildRoute() methods. The Content building APIs can be used while constructing custom layouts and can also be used directly if needed.

buildContent() internally works with the ContentBuilder to use the correct layout configured in the ContentItem. Below you can see a simple FoodMenuItem (subclass of ContentItem) being rendered in a Flutter Widget. The item.description contains PortableText content.

final class FoodItemHero extends StatelessWidget {
final FoodMenuItem item;
const FoodItemHero({super.key, required this.item});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title, style: theme.textTheme.headlineMedium),
Text('${item.calories.toInt()} calories'),
Padding(
padding: EdgeInsets.symmetric(vertical: theme.spacing.s20),
child: vyuh.content.buildContent(context, item.description),
),
],
);
}
}

You can also build the content directly by passing in an instance of a specific ContentItem. This is useful when dealing with APIContent items that render data from an API. Here is an example of a simple WonderItem being rendered from an API call:

import 'package:vyuh_feature_system/vyuh_feature_system.dart' as vf;
class _WonderItem extends StatelessWidget {
const _WonderItem({
required this.document,
});
final WonderMiniInfo document;
@override
Widget build(BuildContext context) {
return vyuh.content.buildContent(
context,
vf.Card(
title: document.title,
description: document.subtitle,
image: document.icon,
action: vc.Action(
configurations: [
NavigationAction(
url: '/wonderous/wonder/${document.identifier}/details',
),
],
),
),
);
}
}

Type Registrations

  • Types can be registered and checked with the register(), isRegistered() methods.

    You would generally not need to use these methods directly unless you are building a very custom ContentItem with a ContentBuilder that leverages custom ContentDescriptor.

  • Deserialization when reading content from CMS.
  • Most of the work of the Content plugin is delegated to the ContentProvider, which takes care of fetching content from the CMS. Once the data is ready, the Content plugin can take over in rendering it on the Flutter widgets.

Using the NavigationPlugin

This plugin can be accessed using the vyuh.router API. The navigation plugin allows you to navigate within the application by pushing and popping routes. Th e plugin internally uses the GoRouter package to do all the navigation. If you are familiar with the GoRouter package, there is nothing new that this plugin is doing besides just wrapping the methods (go(), push(), pop(), replace(), etc.)

The advantage of using this plugin is that it also calls the analytics plugin (vyuh.analytics) wherever needed to track page transitions and send across to the AnalyticsProvider.

Here is an example of a Widget that uses the vyuh.router to push a specific route:

PhotoCard(
photo: photo,
showStats: true,
onTap: () => vyuh.router.push('/unsplash/home/photos/${photo.id}'),
)

Using the NetworkPlugin

This plugin can be accessed using the vyuh.network API. It’s a wrapper around the HTTPClient and allows you to make network calls for all the HTTP verbs (GET, POST, PATCH, PUT, DELETE, OPTIONS, etc.). The advantage of using this plugin is that it creates a singular HTTP client that is useful across the application. This avoids creating multiple copies per feature.

The network API is typically useful when creating API clients for your own bespoke or third-party APIs. Here’s an example of using it with the TMDB API.

Future<GenresListResponse> genres() async {
final response = await vyuh.network.get(apiUrl('genre/movie/list'));
final data = jsonDecode(response.body);
return GenresListResponse.fromJson(data);
}

Using the DIPlugin

Accessible via the vyuh.di API. The Di plug-in is useful for injecting shared dependencies within a feature or even across features. This is a core plugin that allows you to manage stores and services that are required within a feature or across features.

Typically, when you are building a feature, you would do all the DI registrations inside the init() method of the feature. This puts all the dependencies on the global DI container.

However, there might be situations where you would like to have the dependencies only use the scope of a single route. For such scenarios, you can use the Scoped DI, which is accessible inside the RouteLifecycleConfiguration for the route. The lifecycle handler is invoked during the init() and dispose() of individual routes, thus giving you a distinct time-boundary within which the dependencies are active. Note that the Scoped-DI Container for the Route is automatically reset when refreshing a route.

Depending on your needs, you can choose to put your dependencies on the global container or the scoped container.

Here is an example of the TMDB feature that uses the init() to register some feature-level dependencies:

feature_tmdb/feature.dart
final feature = FeatureDescriptor(
name: 'tmdb',
title: 'TMDB',
description:
'Uses the TMDB API to show details of movies with ability to favorite and add to watchlists',
icon: Icons.movie_creation_outlined,
init: () async {
vyuh.di.register(TMDBClient(vyuh.env.get('TMDB_API_KEY')));
vyuh.di.register(TMDBStore());
vyuh.di.register(TmdbSearchStore());
},
// rest of the feature config
);

Using the AuthPlugin

TBD

Using the TelemetryPlugin

TBD

Using the EnvPlugin

The env plugin is useful for loading environment settings from a .env file. We use the flutter_dotenv package from pub.dev to load these settings. The env-plugin makes it convenient for us to load it up automatically and make it available on vyuh.env.

The plugin assumes that you have a .env file in your application package which is loaded automatically at runtime. Also, make sure to include it in your assets section of pubspec.yaml.

pubspec.yaml
flutter:
uses-material-design: true
assets:
- .env
- assets/app-icon.png

As an example, notice how the TMDB feature uses the env plugin to load the TMDB_ API_KEY in the init() method.

feature_tmdb/feature.dart
final feature = FeatureDescriptor(
name: 'tmdb',
title: 'TMDB',
description:
'Uses the TMDB API to show details of movies with ability to favorite and add to watchlists',
icon: Icons.movie_creation_outlined,
init: () async {
final apiKey = vyuh.env.get('TMDB_API_KEY');
vyuh.di.register(TMDBClient(apiKey));
// Rest of the init
},
// rest of the feature config
);

Using the EventPlugin

Refer to the guide on Events