Flutter Web Packages

7 Must Have Flutter Web Packages To Include In Your Next WebApp Build

Jeremy Andrews
FDDevops

--

This is a follow-up to previous article where we spoke about desktop packages for your next web build. You can find a link to that article here. Packages consist of prewritten code that contributes to the Flutter and Dart ecosystems. A package includes dependencies, Dart libraries, apps, resources, tests, images, and examples. A plugin is a special kind of package that makes functionality available to the app.

Provider

If this one is familiar, it is because it was mentioned in our previous article. Instead of writing a lengthy and complex InheritedWidget class, the provider allows for a simplified allocation of resources.

Example Usage

EXPOSING A VALUE AND NEW OBJECT INSTANCE

Provider( create: (_) => MyModel(), child: ... )ChangeNotifierProvider.value( value: MyModel(), child: ... )

Provider allows you expose a an object and also create/listen/dispose of it. To expose a newly created object, use the default constructor of a provider. Do not use the .value constructor if you want to create an object, or you may otherwise have undesired side-effects. Here are some guidelines.

DO create a new object inside create.

DON’T use Provider.value to create your object.

DON’T create your object from variables that can change over the time. In such a situation, your object would never be updated when the value changes.

If you want to pass variables that can change over time to your object, consider using ProxyProvider:

int count; ProxyProvider0( update: (_, ) => MyModel(count), child: ... )int count; Provider( create: (_) => MyModel(count), child: ... )

NOTE:

When using the create/update callback of a provider, it is worth noting that this callback is lazily called by default. What this means is, until the value is requested at least once, the create/update callbacks won’t be called. This behavior can be disabled if you want to pre-compute some logic, using the lazy parameter:

Reusing an existing object instance: If you already have an object instance and want to expose it, you should use the .value constructor of a provider.

MyProvider( create: (_) => Something(), lazy: false, )

Failing to do so may call the dispose method of your object when it is still in use. Here is some guidance.

DO use ChangeNotifierProvider.value to provide an existing ChangeNotifier.

DON’T reuse an existing ChangeNotifier using the default constructor.

MyChangeNotifier variable; ChangeNotifierProvider.value( value: variable, child: ...) MyChangeNotifier variable; ChangeNotifierProvider( create: (_) => variable, child: ... )

Reading a value

The easiest way to read a value is by using the extension methods on [BuildContext]:

  1. context.watch(), which makes the widget listen to changes on T
  2. context.read(), which returns T without listening to it
  3. context.select<T, R>(R cb(T value)), which allows a widget to listen to only a small part of T.

You can also use the static method Provider.of(context), which behaves in a similar manner to context.watch() and when you pass "false" to the listen parameter (ie. Provider.of(context,listen: false) it will behave similarly to read. It's worth noting that context.read() won't rebuild the widget when the value changes. In addition, context.read() cannot be called inside StatelessWidget.build/State.build. However, it can be called outside of these methods.

The methods described above will start searching the widget tree from the 1st widget that is associated with the BuildContext passed, and will return the nearest variable of type T found (or throw if nothing is found).

It’s worth noting that this operation is O(1). It doesn’t involve actually walking in the widget tree. Combined with the first example of exposing a value, this widget will read the exposed String and render “Hello World.”

class Home extends StatelessWidget { @override Widget build(BuildContext context) { return Text( // Don't forget to pass the type of the object you want to obtain to `watch`! context.watch<String>(), ); }}

Alternatively, instead of using these methods, we can use Consumer and Selector. These can be useful for performance optimizations or when it is difficult to obtain a BuildContext descendant of the provider.

MultiProvider

When injecting many values in big applications, Provider can rapidly become nested:

Provider<Something>( create: (_) => Something(), child: Provider<SomethingElse>( create: (_) => SomethingElse(), child: Provider<AnotherThing>( create: (_) => AnotherThing(), child: someWidget, ), ), ),MultiProvider( providers: [ Provider<Something>(create: (_) => Something()), Provider<SomethingElse>(create: (_) => SomethingElse()), Provider<AnotherThing>(create: (_) => AnotherThing()), ], child: someWidget, )

The behavior of both examples is strictly the same. MultiProvider only changes the appearance of the code.

ProxyProvider

ProxyProvider is a provider that combines multiple values from other providers into a new object, and sends the result to Provider. That new object is then updated whenever one of the providers depends on updates. The following example uses ProxyProvider to build translations based on a counter coming from another provider.

Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => Counter()), ProxyProvider<Counter, Translations>( update: (_, counter, ) => Translations(counter.value), ), ], child: Foo(), ); } class Translations { const Translations(this._value); final int _value; String get title => 'You clicked $_value times'; }

There are several flavors: ProxyProvider vs ProxyProvider2 vs ProxyProvider3 . The digit after the class name is the number of other providers that ProxyProvider depends on.

ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider, They all work similarly, but instead of sending the result into a Provider, the ChangeNotifierProxyProvider will send its value to a ChangeNotifierProvider.

URL launcher for web

URL launcher’s core function is to send users to URLs on a single tap. It can also be used to send a user to a specific email address.

In order to install the URL launcher plugin in your web application, a dependency needs to be added in the pubcpec.yaml file.

Once you have the url launcher web dependency in your pubspec, you should be able to use package url as normal. keep in mind that Flutter web and Flutter Mobile do diverge in some cases, so when using packages ensure you have the right dependencies listed.

dependencies:url_launcher: ^5.1.4
url_launcher_web: ^0.1.0

Flutter_bloc: 7.0.0

Bloc makes it easy to separate presentation from business logic, making your code fast, easy to test, and reusable. When building production quality applications, managing state becomes critical. As developers we want to know what state our application is in at any point in time, easily test every case to make sure our app is responding appropriately, record every single user interaction in our application so that we can make data-driven decisions, and work as efficiently as possible by reusing components both within our application and across other applications.

This package allows developers to develop apps faster by following the same patterns and conventions throughout an entire codebase.

Example Usage

We can integrate the CounterPage widget in CounterCubit by using the BlocBuilder.

counter_cubit.dart

class CounterCubit extends Cubit<int> { CounterCubit() : super(0); void increment() => emit(state + 1); void decrement() => emit(state - 1); }

counter_page.dart

class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Counter')), body: BlocBuilder<CounterCubit, int>( builder: (context, count) => Center(child: Text('$count')), ), floatingActionButton: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: FloatingActionButton( child: const Icon(Icons.add), onPressed: () => context.read<CounterCubit>().increment(), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: FloatingActionButton( child: const Icon(Icons.remove), onPressed: () => context.read<CounterCubit>().decrement(), ), ), ], ), ); }}

At this point we have successfully separated our presentational layer from our business logic layer. Notice that the CounterPage widget knows nothing about what happens when a user taps the buttons. The widget simply tells the CounterCubit that the user has pressed either the increment or decrement button.

Flutter_blurhash 0.6.0

This package provides a compact representation of a placeholder for an image. Constrain your widget render area and let BlurHash fill the pixels.

class BlurHashApp extends StatelessWidget { const BlurHashApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) => MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("BlurHash")), body: const SizedBox.expand( child: Center( child: AspectRatio( aspectRatio: 1.6, child: BlurHash(hash: "L5H2EC=PM+yV0g-mq.wG9c010J}I"), ), ), ), ), ); }

Location

This package shares the location with the user and features call backs whenever location is changed. To start using this plugin add a dependency to the package’s pubspec.yaml file. This package works directly out of the box for Flutter Web

Example Usage

import 'package:location/location.dart';

In order to request the location, manually check the Location Service status and Permission status.

Location location = new Location(); bool _serviceEnabled; PermissionStatus _permissionGranted; LocationData _locationData;

For requiring continuous callbacks on change of location:

location.onLocationChanged.listen((LocationData currentLocation) { // Use current location }); _serviceEnabled = await location.serviceEnabled(); if (!_serviceEnabled) { _serviceEnabled = await location.requestService(); if (!_serviceEnabled) { return; } } _permissionGranted = await location.hasPermission(); if (_permissionGranted == PermissionStatus.denied) { _permissionGranted = await location.requestPermission(); if (_permissionGranted != PermissionStatus.granted) { return; } } _locationData = await location.getLocation();

Originally published at https://thestartupguy.dev.

--

--

Jeremy Andrews
FDDevops

Jeremy Andrews is a technology entrepreneur specializing in product, finance, and software development. Connect with him on discord https://discord.gg/XNvmnyd7