An Example Of A WebSocket Server Created with Dart

Dart WebSocket Example (Server & Client)

An example of a WebSocket Server created with Dart language and a client that can connect to it.

Run Server

Open terminal in project directory

>> dart ./bin/server.dart

Example Output:

H:\AndroidStudioProjects\werbsocket_server>dart ./bin/server.dart  
Listening on localhost:4040

Connection Server

Open terminal in project directory

>> dart ./bin/client.dart

In Postman

image


Download details:

Author: kerimbr
Source code: https://github.com/kerimbr/dart_websocket_example

#fluter #dart 

An Example Of A WebSocket Server Created with Dart
Antwan  Larson

Antwan Larson

1655587980

How to Create and Publish Dart Packages

In this video tutorial, We'll show you How to Create and Publish Dart Packages With Step by Step for beginners.

#Dart #fluter 

How to Create and Publish Dart Packages
Antwan  Larson

Antwan Larson

1655551751

How to Print or save widgets as documents in Flutter app

In this video tutorial, We'll show you How to Print or save widgets as documents in Flutter app With Step by Step.

👩‍💻Source Code: https://github.com/vijayinyoutube/print_flutterapp

#fluter 

How to Print or save widgets as documents in Flutter app

Nsd Android: Flutter Network Service Discovery Plugin

nsd_android

The Android implementation of the nsd plugin.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add nsd_android

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  nsd_android: ^1.2.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

example/EXAMPLE.md

Head over to the plugin main page for a usage example.


Author: sebastianhaberey
Source code: https://github.com/sebastianhaberey/nsd/tree/main/nsd_android
License:

#dart #fluter 

Nsd Android: Flutter Network Service Discovery Plugin
Jack  Shaw

Jack Shaw

1654092000

Boustro: A Rich Text Editor for Flutter

boustro

Boustro is a rich text editor for Flutter.

Features

Easy to customize

Boustro is designed to be extremely customizable. It provides the base infrastructure to implement any custom components in user code. To be usable out of the box, boustro contains some built-in components that you'd typically expect from a rich text editor.

Documentation on writing custom components has not yet been written. For now, check out the implementation of the built-in components.

Themeable with dark and light defaults

Boustro defines extensible theming classes that let users customize the base editor, as well as any components implemented outside of boustro itself. These theme classes even support lerping, for a nice animation when switching themes.

LightDark
boustro example lightboustro example dark

Cross-platform

Boustro builds on Flutter's built-in text widgets, without any custom rendering or input handling, so it runs on all platforms supported by Flutter.

Getting Started

Check out the docs or the example.

Glossary and Concepts

  • Document: Immutable representation of a rich text document.
  • Paragraph: Can be either a line of text or (non-inline) embed.
  • Line: A line of text with rich formatting.
  • Embed: Any content in a document that is not a Line.
  • Line modifier: Wraps a line and can change the way it's displayed.
  • Text attribute: Applied to text to set its formatting or add gesture recognizers.
  • Span: Text range with a text attribute and rules for how the range behaves when text is inserted (whether it is expanded or not).

Limitations

  • Can't select across lines. I might be able to fix this issue and make line handling (newlines and backspace at the start to delete lines) less hacky by using only a single TextField. However, this would greatly complicate the line paragraph system, and I'm not sure that's worth it.
  • At most 1 gesture per TextSpan. TextSpan can have a gesture recognizer, but not multiple. We can solve this by using a WidgetSpan that wraps a GestureRecognizer, that wraps the actual text span, but that's blocked by:
  • The same issues prevent me from creating inline embeds (e.g. inline images) using WidgetSpan. Please go upvote these issues if you'd like to see these limitations overcome.

Alternatives

Keep a Changelog + SemVer

The changelog documents all notable changes.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add boustro

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  boustro: ^0.2.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:boustro/boustro.dart';

example/lib/main.dart

// ignore_for_file: diagnostic_describe_all_properties, use_key_in_widget_constructors, public_member_api_docs
import 'package:boustro/boustro.dart';
import 'package:boustro/toolbar_items.dart' as toolbar_items;
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

import 'theme.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  /// The attribute theme is used to customize the effect of [TextAttribute]s.
  AttributeThemeData _buildAttributeTheme(BuildContext context) {
    final builder = AttributeThemeBuilder();
    builder.boldFontWeight = FontWeight.w900;
    builder.linkOnTap = _handleLinkTap;
    return builder.build();
  }

  /// The component config is used to customize embeds and line modifiers.
  BoustroComponentConfigData _buildComponentConfig(BuildContext context) {
    final builder = BoustroComponentConfigBuilder();
    builder.imageMaxHeight = 400;
    builder.imageSideColor = Theme.of(context).brightness == Brightness.light
        ? Colors.brown.withOpacity(0.2)
        : Colors.deepPurple.shade900.withOpacity(0.2);
    return builder.build();
  }

  @override
  Widget build(BuildContext context) {
    return ThemeBuilder(
      builder: (context, themeMode, child) {
        return BoustroConfig(
          attributeTheme: _buildAttributeTheme(context),
          componentConfigData: _buildComponentConfig(context),
          builder: (context) => MaterialApp(
            title: 'Flutter Demo',
            theme: lightTheme,
            darkTheme: darkTheme,
            themeMode: themeMode,
            home: HomeScreen(),
            debugShowCheckedModeBanner: false,
          ),
        );
      },
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final controller = DocumentController();

  @override
  Widget build(BuildContext context) {
    // BoustroConfig wraps the three theming classes and provides
    // an implicit animation to switch between themes.
    return Scaffold(
      appBar: AppBar(
        title: const Text('Boustro'),
        actions: [
          IconButton(
            icon: const Icon(Icons.check),
            onPressed: _showPreview,
          ),
        ],
      ),
      // A BoustroScaffold lays out the editor and toolbar, and ensures
      // toolbar clicks don't cause the editor to lose focus.

      // To maintain focus without a scaffold, wrap the area that should keep
      // the editor focused when clicked in a FocusTrapArea and pass it the
      // focusNode of your DocumentController.
      body: BoustroScaffold(
        focusNode: controller.focusNode,
        // The auto formatter automatically applies attributes to text
        // matching regular expressions. Some patterns are provided
        // in CommonPatterns for convenience.
        editor: AutoFormatter(
          controller: controller,
          rules: [
            FormatRule(CommonPatterns.hashtag, (_) => boldAttribute),
            FormatRule(CommonPatterns.mention, (_) => italicAttribute),
            FormatRule(CommonPatterns.httpUrl, (_) => boldAttribute),
          ],
          // DocumentEditor is the main editor class. It manages the
          // paragraphs that are either embeds (custom widgets) or
          // TextFields with custom TextEditingControllers that manage
          // spans for formatting.
          child: DocumentEditor(
            controller: controller,
          ),
        ),
        // The Toolbar contains buttons that can modify the document using
        // the DocumentController. There are built-in ToolbarItems for the
        // boustro_starter components. Toolbar has support for nested menus
        // (try the image button).
        toolbar: Toolbar(
          documentController: controller,
          defaultItemBuilder: _defaultToolbarItemBuilder,
          items: [
            toolbar_items.bold,
            toolbar_items.italic,
            toolbar_items.underline,
            toolbar_items.link(),
            toolbar_items.title,
            toolbar_items.image(
              pickImage: (_) async => const NetworkImage(
                  'https://upload.wikimedia.org/wikipedia/commons/1/19/Billy_Joel_Shankbone_NYC_2009.jpg'),
              snapImage: (_) async => const NetworkImage(
                  'https://upload.wikimedia.org/wikipedia/commons/1/19/Billy_Joel_Shankbone_NYC_2009.jpg'),
            ),
            toolbar_items.bulletList,
            ToolbarItem(
              title: const Icon(Icons.wb_sunny),
              onPressed: (context, __) =>
                  ThemeModeScope.of(context).toggle(context),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _showPreview() async {
    await Navigator.of(context).push<void>(
      MaterialPageRoute<void>(
        builder: (context) => Scaffold(
          appBar: AppBar(title: const Text('Preview')),
          // DocumentView displays a document readonly.
          body: DocumentView(
            document: controller.toDocument(),
          ),
        ),
      ),
    );
  }

  Widget _defaultToolbarItemBuilder(
    BuildContext context,
    DocumentController controller,
    ToolbarItem item,
  ) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 2),
      child: Center(
        child: IconButton(
          splashColor: Colors.transparent,
          onPressed: item.onPressed == null
              ? null
              : () => item.onPressed!(context, controller),
          icon: item.title!,
          tooltip: item.tooltip,
        ),
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

Future<void> _handleLinkTap(BuildContext context, String url) async {
  if (await canLaunch(url)) {
    await launch(url);
  }
}

final darkTheme = ThemeData(
  colorScheme: ColorScheme.dark(
    surface: Colors.grey.shade800,
  ),
  dividerColor: Colors.black12,
);

final lightTheme = ThemeData(
  colorScheme: ColorScheme.light(
    primary: Colors.blue,
    onPrimary: Colors.grey.shade800,
  ),
  appBarTheme: AppBarTheme(backgroundColor: Colors.grey.shade200),
  dividerColor: Colors.grey.shade300,
);

Author: Jjagg
Source code: https://github.com/Jjagg/boustro
License: MIT license

#fluter #dart 

Boustro: A Rich Text Editor for Flutter
Zoie  Trantow

Zoie Trantow

1647334800

Reactive forms Generator: Code Generator For Reactive_forms

Reactive Forms Generator

Welcome to ReactiveFormsGenerator, code generator for reactive_forms which will save you tons of time and make your forms type safe.

There is no reason write code manually! Let the code generation work for you.

Motivation

One of the goals of this package is to make reactive_forms package even more cool and fun to use.

Let's see what issues this package tries to mitigate.

Here is how typical reactive_forms form looks like

/// form instantiation
FormGroup buildForm() => fb.group(<String, Object>{
  'email': FormControl<String>(
    validators: [Validators.required, Validators.email],
  ),
  'password': ['', Validators.required, Validators.minLength(8)],
  'rememberMe': false,
});

/// form itself
final form = ReactiveFormBuilder(
  form: buildForm,
  builder: (context, form, child) {
    return Column(
      children: [
        ReactiveTextField<String>(
          formControlName: 'email',
        ),
        const SizedBox(height: 16.0),
        ReactiveTextField<String>(
          formControlName: 'password',
        ),
        const SizedBox(height: 16.0),
        ElevatedButton(
          onPressed: () {
            if (form.valid) {
              print(form.value);
            } else {
              form.markAllAsTouched();
            }
          },
          child: const Text('Sign Up'),
        ),
        ElevatedButton(
          onPressed: () => form.resetState({
            'email': ControlState<String>(value: null),
            'password': ControlState<String>(value: null),
            'rememberMe': ControlState<bool>(value: false),
          }, removeFocus: true),
          child: const Text('Reset all'),
        ),
      ],
    );
  },
);

First issue is String identifiers which is used to define fields. Technically you can extract them into separate class, enum or whatever you like. But this is manual work which you have to do each time you create the form. The other disadvantage is when you refer to any field by his String identifier you loos static type check. There is no way for static analyser to check if some random field name login is suitable to put in particular widget. So you can easily get the form which looks ok but fails to build due to the typo in field names and putting login field into ReactiveCheckbox field. Isn't it better the code generation to do it for you?

Second issue is output which is always Map<String, Object>. It is ok for languages like JS. But for the typed language you would prefer to get the output fom the form like model. And avoid manual type casting like this one.

final document = DocumentInput(
  subTypeId: form.value["subType"] as DocumentSubTypeMixin,
  documentNumber: form.value["documentNumber"] as String,
  countryIsoCode: form.value["country"] as CountryMixin,
  countryOfIssueIsoCode: form.value["country"] as CountryMixin,
  issueDate: form.value["issueDate"] as DateTime,
  vesselId: form.value["vessel"] as VesselMixin,
);

This is two main issues that forced me to write this generator. In the next chapters of documentation you'll see how we define and annotate the model which describes the form state and how easy and elegant it works with a bit of magic from code generation.

How to use

Minimum Requirements

  • Dart SDK: >=2.12.0 <3.0.0
  • Flutter: >= 2.2.0

Installation

To use [reactive_forms_generator], you will need your typical [build_runner]/code-generator setup.
First, install [build_runner] and [reactive_forms_generator] by adding them to your pubspec.yaml file:

# pubspec.yaml
dependencies:
  reactive_forms:
  reactive_forms_annotations:

dev_dependencies:
  build_runner:
  reactive_forms_generator:

This installs three packages:

Ignore lint warnings on generated files

It is likely that the code generated by [reactive_forms_generator] will cause your linter to report warnings.

The solution to this problem is to tell the linter to ignore generated files, by modifying your analysis_options.yaml:

analyzer:
  exclude:
    - "**/*.gform.dart"

Run the generator

Note that like most code-generators, reactive_forms_generator will need you to both import the annotation (meta) and use the part keyword on the top of your files.

Make sure you added required imports before running the generator

import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

part 'my_file.gform.dart';

To run the code generator you have two possibilities:

  • If your package depends on Flutter:
    • flutter pub run build_runner build
  • If your package does not depend on Flutter:
    • dart pub run build_runner build

Features

Basics

Let's start from simple login form.

First we need to define our form model

Model

class Basic {
  final String email;

  final String password;

  Basic({this.email = '', this.password = ''});
}

We defined here a simple model with non-nullable email and password fields.

Annotation

The next step is to add annotations to help generator do his job.

import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@ReactiveFormAnnotation()
class Basic {
  final String email;

  final String password;

  Basic({
    @FormControlAnnotation()
    this.email = '',

    @FormControlAnnotation()
    this.password = '',
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormControlAnnotation - maps fields to control elements.

Validation

The login form should not proceed if there is any empty values. We need to modify our code to add some required validators.

import 'package:example/helpers.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@ReactiveFormAnnotation()
class Basic {
  final String email;

  final String password;

  Basic({
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
    this.email = '',
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
    this.password = '',
  });
}

As far as we are using annotations - validators should be top level functions or static class fields.

Now we are ready to run our form generator. You can check output here.

Form

Let's build our form based on generated code

final form = BasicFormBuilder(
  // setup form model with initial data
  model: Basic(),
  // form builder
  builder: (context, formModel, child) {
    return Column(
      children: [
        ReactiveTextField<String>(
          formControl: formModel.emailControl,
          validationMessages: (control) => {
            ValidationMessage.required: 'The email must not be empty',
          },
          decoration: const InputDecoration(labelText: 'Email'),
        ),
        const SizedBox(height: 8.0),
        ReactiveTextField<String>(
          formControl: formModel.passwordControl,
          obscureText: true,
          validationMessages: (control) => {
            ValidationMessage.required: 'The password must not be empty',
          },
          textInputAction: TextInputAction.done,
          decoration: const InputDecoration(labelText: 'Password'),
        ),
        const SizedBox(height: 8.0),
        ReactiveBasicFormConsumer(
          builder: (context, form, child) {
            return ElevatedButton(
              child: Text('Submit'),
              onPressed: form.form.valid
                  ? () {
                print(form.model.email);
                print(form.model.password);
              }
                  : null,
            );
          },
        ),
      ],
    );
  },
);

BasicFormBuilder - generated widget that injects form into context ReactiveTextField - bundled text fields ReactiveBasicFormConsumer - generated widget that rebuilds upon form change

You can get access to prefilled form model by calling form.model.[field-name].

Dynamic forms with FormArray

The next example will show how to build dynamic forms. We will create a mailing list which will allow adding new email and basic validation.

Model

The model is pretty simple.

class MailingList {
  final List<String?> emailList;

  MailingList({
    this.emailList = const [],
  });
}

Annotation

The next step is to add annotations to help generator do his job.

import 'package:example/helpers.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@ReactiveFormAnnotation()
class MailingList {
  final List<String?> emailList;

  MailingList({
    @FormArrayAnnotation(
      validators: const [
        mailingListValidator,
      ],
      // you can also add validators for each item of the array
      // it will be added to each FormControl
      // itemValidators: [
      //   emailValidator,
      // ],
    )
    this.emailList = const [],
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormArrayAnnotation - maps fields to control elements.

Validation

The mailing list form should not be valid in two cases - if there are duplicates and if any field is invalid email.

/// simple regexp to validate email
final emailRegex = RegExp(
    r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');

/// validator that validates field against email regex
Map<String, dynamic> emailValidator(AbstractControl<dynamic> control) {
  final email = control.value as String?;

  return email != null && emailRegex.hasMatch(email)
      ? <String, dynamic>{}
      : <String, dynamic>{ValidationMessage.email: true};
}

/// validates there is no duplicates in email list and each item is valid email
Map<String, dynamic>? mailingListValidator(AbstractControl control) {
  final formArray = control as FormArray<String>;
  final emails = formArray.value ?? [];
  final test = Set<String>();

  // sets errors for each input in case if value is invalid email
  formArray.controls.forEach((e) => e.setErrors(emailValidator(e)));

  // checks that there is no duplicates
  final result = emails.fold<bool>(true,
        (previousValue, element) => previousValue && test.add(element ?? ''),
  );

  return result ? null : <String, dynamic>{'emailDuplicates': true};
}

As far as we are using annotations - validators should be top level functions or static class fields.

Now we are ready to run our form generator. You can check output here.

Form

Let's build our form based on generated code

// create form based on generated widget
final form = MailingListFormBuilder(
  // instantiate with empty model
  model: MailingList(),
  builder: (context, formModel, child) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              // renders list of fields corresponding to added elements
              child: ReactiveFormArray<String>(
                formArray: formModel.emailListControl,
                builder: (context, formArray, child) => Column(
                  children: formModel.emailListValue
                      .asMap()
                      .map((i, email) {
                    return MapEntry(
                        i,
                        ReactiveTextField<String>(
                          formControlName: i.toString(),
                          validationMessages: (_) => {
                            'email': 'Invalid email',
                          },
                          decoration: InputDecoration(
                              labelText: 'Email ${i}'),
                        ));
                  })
                      .values
                      .toList(),
                ),
              ),
            ),
            SizedBox(width: 16),
            // adds new item to the list of fields
            ElevatedButton(
              onPressed: () {
                // add****Item function allows you not only to add values but also alter the FormControl parameters if needed
                // you can change 
                //  - validators,
                //  - asyncValidators,
                //  - asyncValidatorsDebounceTime,
                //  - disabled,
                // validatorsApplyMode - controls how the validators will be applied
                //  - merge - will append to the validators defined in annotation
                //  - override - will override to the validators defined in annotation
                formModel.addEmailListItem('');
              },
              child: const Text('add'),
            )
          ],
        ),
        SizedBox(height: 16),
        // renders error related to the whole list of elements
        ReactiveMailingListFormConsumer(
          builder: (context, form, child) {
            // map error keys to text
            final errorText = {
              'emailDuplicates': 'Two identical emails are in the list',
            };
            final errors = <String, dynamic>{};

            // filter values related to individual text fields
            form.emailListControl.errors.forEach((key, value) {
              final intKey = int.tryParse(key);
              if (intKey == null) {
                errors[key] = value;
              }
            });

            // if there is still erros left - render an error message 
            if (form.emailListControl.hasErrors && errors.isNotEmpty) {
              return Text(errorText[errors.entries.first.key] ?? '');
            } else {
              return Container();
            }
          },
        ),
        SizedBox(height: 16),
        Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            ElevatedButton(
              onPressed: () {
                if (formModel.form.valid) {
                  print(formModel.model);
                } else {
                  formModel.form.markAllAsTouched();
                }
              },
              child: const Text('Sign Up'),
            ),
            ReactiveMailingListFormConsumer(
              builder: (context, form, child) {
                return ElevatedButton(
                  child: Text('Submit'),
                  onPressed: form.form.valid ? () {} : null,
                );
              },
            ),
          ],
        )
      ],
    );
  },
);

Nested forms with FormGroups

The next example will show how to build nested forms. We will create a user profile form with first/last names and home/office addresses. Address will contain city/street/zip fields.

Model

The model will be separated on two parts UserProfile and Address

class UserProfile {
  final String firstName;

  final String lastName;

  final Address? home;

  final Address? office;

  UserProfile({
    this.firstName = '',
    this.lastName = '',
    this.home,
    this.office,
  });
}

class Address {
  final String? street;

  final String? city;

  final String? zip;

  Address({
    this.street,
    this.city,
    this.zip,
  });
}

Annotation

The next step is to add annotations to help generator do his job.

import 'package:example/helpers.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@ReactiveFormAnnotation()
class UserProfile {
  final String firstName;

  final String lastName;

  final Address? home;

  final Address? office;

  UserProfile({
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
    this.firstName = '',
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
    this.lastName = '',
    this.home,
    this.office,
  });
}

@FormGroupAnnotation()
class Address {
  final String? street;

  final String? city;

  final String? zip;

  Address({
    @FormControlAnnotation()
    this.street,
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
    this.city,
    @FormControlAnnotation()
    this.zip,
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormGroupAnnotation - describes the nested form.

Validation

We will use only simple requiredValidator for first/last names and city.

Map<String, dynamic>? requiredValidator(AbstractControl<dynamic> control) {  return Validators.required(control); }

As far as we are using annotations - validators should be top level functions or static class fields.

Now we are ready to run our form generator. You can check output here.

Form

Let's build our form based on generated code

// create form based on generated widget
final form = UserProfileFormBuilder(
  model: UserProfile(),
  builder: (context, formModel, child) {
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          ReactiveTextField<String>(
            formControl: formModel.firstNameControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'First name',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.lastNameControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Last name',
            ),
          ),
          const SizedBox(height: 24.0),
          Text('Home address', style: TextStyle(fontSize: 18)),
          ReactiveTextField<String>(
            formControl: formModel.homeForm.cityControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Home city',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.homeForm.streetControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Home street',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.homeForm.zipControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            textInputAction: TextInputAction.done,
            decoration: const InputDecoration(
              labelText: 'Home zip',
            ),
          ),
          const SizedBox(height: 8.0),
          Text('Office address', style: TextStyle(fontSize: 18)),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.officeForm.cityControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Office city',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.officeForm.streetControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Office street',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.officeForm.zipControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Office zip',
            ),
          ),
          ElevatedButton(
            onPressed: () {
              if (formModel.form.valid) {
                print(formModel.model);
              } else {
                formModel.form.markAllAsTouched();
              }
            },
            child: const Text('Sign Up'),
          ),
          ReactiveUserProfileFormConsumer(
            builder: (context, form, child) {
              return ElevatedButton(
                child: Text('Submit'),
                onPressed: form.form.valid
                    ? () {
                  print(form.model.firstName);
                  print(form.model.lastName);
                }
                    : null,
              );
            },
          ),
        ],
      ),
    );
  },
);

Nested forms with array of FormGroups

The next example will show how to build nested forms. We will create a delivery list with simple control for name and form group for address; Address will contain city/street fields.

Model

The model will be separated on three parts DeliveryList, DeliveryPoint and Address

class DeliveryList {
  final List<DeliveryPoint> deliveryList;

  DeliveryList({
    this.deliveryList = const [],
  });
}

class DeliveryPoint {
  final String name;

  final Address? address;

  DeliveryPoint({
    this.name = '',
    this.address,
  });
}

class Address {
  final String? street;

  final String? city;

  Address({
    this.street,
    this.city,
  });
}

Annotation

The next step is to add annotations to help generator do his job.

@ReactiveFormAnnotation()
class DeliveryList {
  final List<DeliveryPoint> deliveryList;

  DeliveryList({
    @FormArrayAnnotation() this.deliveryList = const [],
  });
}

@FormGroupAnnotation()
class DeliveryPoint {
  final String name;

  final Address? address;

  DeliveryPoint({
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
    this.name = '',
    this.address,
  });
}

@FormGroupAnnotation()
class Address {
  final String? street;

  final String? city;

  Address({
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
    this.street,
    @FormControlAnnotation()
    this.city,
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormGroupAnnotation - describes the nested form.

Now we are ready to run our form generator. You can check output here.

Form

Let's build our form based on generated code

// create form based on generated widget
final form = DeliveryListFormBuilder(
  model: DeliveryList(),
  builder: (context, formModel, child) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              child: ReactiveFormArray<Map<String, Object?>>(
                formArray: formModel.deliveryListControl,
                builder: (context, formArray, child) {
                  return Column(
                    children: formModel.deliveryListValue
                        .asMap()
                        .map((i, deliveryPoint) {
                      return MapEntry(
                          i,
                          Column(
                            children: [
                              ReactiveTextField<String>(
                                formControlName: '${i}.name',
                                validationMessages: (_) => {
                                  ValidationMessage.required:
                                  'Must not be empty',
                                },
                                decoration: InputDecoration(
                                  labelText: 'Name ${i}',
                                ),
                              ),
                              ReactiveTextField<String>(
                                formControlName:
                                '${i}.address.street',
                                validationMessages: (_) => {
                                  ValidationMessage.required:
                                  'Must not be empty',
                                },
                                decoration: InputDecoration(
                                  labelText: 'Street ${i}',
                                ),
                              ),
                              ReactiveTextField<String>(
                                formControlName: '${i}.address.city',
                                validationMessages: (_) => {
                                  ValidationMessage.required:
                                  'Must not be empty',
                                },
                                decoration: InputDecoration(
                                  labelText: 'City ${i}',
                                ),
                              ),
                            ],
                          ));
                    })
                        .values
                        .toList(),
                  );
                },
              ),
            ),
            SizedBox(width: 16),
            ElevatedButton(
              onPressed: () {
                formModel.addDeliveryListItem(DeliveryPoint());
              },
              child: const Text('add'),
            )
          ],
        ),
        SizedBox(height: 16),
        SizedBox(height: 16),
        Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            ElevatedButton(
              onPressed: () {
                if (formModel.form.valid) {
                  print(formModel.model);
                } else {
                  formModel.form.markAllAsTouched();
                }
              },
              child: const Text('Sign Up'),
            ),
            ReactiveDeliveryListFormConsumer(
              builder: (context, form, child) {
                return ElevatedButton(
                  child: Text('Submit'),
                  onPressed: form.form.valid ? () {} : null,
                );
              },
            ),
          ],
        )
      ],
    );
  },
);

Freezed

You can annotate models produced by Freezed with additional annotations to generate the form Read more about freezed here

First we need to define our form model

Model

@freezed
class FreezedClass with _$FreezedClass {
  const factory FreezedClass({
    String? id,
    String? name,
    double? year,
  }) = _FreezedClass;

  factory FreezedClass.fromJson(Map<String, dynamic> json) =>
      _$FreezedClassFromJson(json);
}

Annotation

The next step is to add annotations to help generator do his job.

import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'freezed_class.g.dart';
part 'freezed_class.freezed.dart';

@freezed
@ReactiveFormAnnotation()
class FreezedClass with _$FreezedClass {
  const factory FreezedClass({
    @FormControlAnnotation() String? id,
    @FormControlAnnotation() String? name,
    @FormControlAnnotation() double? year,
  }) = _FreezedClass;

  factory FreezedClass.fromJson(Map<String, dynamic> json) =>
      _$FreezedClassFromJson(json);
}

Validation

You can add validations the same way as in basics example

Form

Let's build our form based on generated code

final form = FreezedClassFormBuilder(
  model: FreezedClass(),
  builder: (context, formModel, child) {
    return Column(
      children: [
        ReactiveTextField<String>(
          formControl: formModel.idControl,
          textInputAction: TextInputAction.next,
          decoration: const InputDecoration(
            labelText: 'ID',
            helperText: '',
            helperStyle: TextStyle(height: 0.7),
            errorStyle: TextStyle(height: 0.7),
          ),
        ),
        const SizedBox(height: 16.0),
        ReactiveTextField<String>(
          formControl: formModel.nameControl,
          textInputAction: TextInputAction.done,
          decoration: const InputDecoration(
            labelText: 'Name',
            helperText: '',
            helperStyle: TextStyle(height: 0.7),
            errorStyle: TextStyle(height: 0.7),
          ),
        ),
        const SizedBox(height: 16.0),
        ReactiveSlider(
          formControl: formModel.yearControl,
          min: 1900,
          max: 2100,
        ),
        ElevatedButton(
          onPressed: () {
            if (formModel.form.valid) {
              print(formModel.model);
              print(formModel.model.year);
            } else {
              formModel.form.markAllAsTouched();
            }
          },
          child: const Text('Sign Up'),
        ),
        ReactiveFreezedClassFormConsumer(
          builder: (context, formModel, child) {
            return ElevatedButton(
              child: Text('Submit'),
              onPressed: formModel.form.valid ? () {} : null,
            );
          },
        ),
      ],
    );
  },
);

Subforms

There could be the case when some of your nested entities will have to have their own subform with subset of fields. Let's refer to our previous example of Nested forms with array of FormGroups

We used DeliveryPoint entity to create a list for points for delivery. If you want to have the set of form classes to be generated for DeliveryPoint just add and additional ReactiveFormAnnotation annotation

@ReactiveFormAnnotation()
@FormGroupAnnotation()
class DeliveryPoint {
  final String name;

  final Address? address;

  DeliveryPoint({
    @FormControlAnnotation(
      validators: const [requiredValidator],
    )
        this.name = '',
    this.address,
  });
}

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add reactive_forms_generator

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  reactive_forms_generator: ^0.9.6-beta

Alternatively, your editor might support dart pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:reactive_forms_generator/reactive_forms_generator.dart';

example/lib/main.dart

import 'package:example/docs/delivery_list/delivery_point_route_form.dart';
import 'package:example/docs/delivery_list/delivery_route_form.dart';
import 'package:example/docs/mailing_list/mailing_list_form.dart';
import 'package:example/docs/freezed/freezed_form.dart';
import 'package:example/docs/user_profile/user_profile_form.dart';
import 'package:example/routes/array_nullable_form.dart';
import 'package:example/routes/group_nullable_group.dart';
import 'package:example/routes/login_form.dart';
import 'package:example/routes/login_nullable_form.dart';
import 'package:example/docs/basic/basic_form.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class Routes {
  static const login = '/login';

  static const basic = '/basic';

  static const mailingList = '/mailing-list';

  static const userProfile = '/user-profile';

  static const group = '/group';

  static const deliveryList = '/delivery-list';

  static const deliveryPoint = '/delivery-point';

  static const loginNullable = '/login-nullable';

  static const arrayNullable = '/array-nullable';

  static const freezed = '/freezed';
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      routes: <String, WidgetBuilder>{
        Routes.login: (_) => LoginFormWidget(),
        Routes.basic: (_) => BasicFormWidget(),
        Routes.mailingList: (_) => MailingListFormWidget(),
        Routes.userProfile: (_) => UserProfileFormWidget(),
        Routes.loginNullable: (_) => LoginNullableFormWidget(),
        Routes.arrayNullable: (_) => ArrayNullableFormWidget(),
        Routes.group: (_) => GroupNullableFormWidget(),
        Routes.deliveryList: (_) => DeliveryListFormWidget(),
        Routes.deliveryPoint: (_) => DeliveryPointWidget(),
        Routes.freezed: (_) => FreezedFormWidget(),
      },
      home: DeliveryListFormWidget(),
    );
  }
}

Author: artflutter
Source Code: https://github.com/artflutter/reactive_forms_generator
License: View license

#dart #fluter 

Reactive forms Generator: Code Generator For Reactive_forms
Bulah  Pfeffer

Bulah Pfeffer

1632541370

How to Create Modal Bottom Sheet in Flutter

Learn how to create a modal bottom sheet in Flutter with this short guide, complete with practical examples.

In this tutorial, we’ll demonstrate how to create a modal bottom sheet in Flutter with practical examples.

Here’s what we’ll cover:

  • Types of bottom sheets in Flutter
  • What is a modal bottom sheet?
  • What is a modal bottom sheet used for?
  • The showModalBottomSheet function
  • How to create a modal bottom sheet in Flutter

#fluter

How to Create Modal Bottom Sheet in Flutter
Antwan  Larson

Antwan Larson

1629821018

How to Create Login and Register Using Flutter Firebase Auth with Bloc

Learn How to Create Login anđ Register Using Flutter Firebase Auth with Bloc

▶SourceCode
* Github
https://github.com/PuzzleLeaf/FlutterFirebaseAuthWithBloc

▶Timestamp
00:00 - Intro
00:20 - Main
00:30 - LoginScreen
01:02 - CurvedWidget, ClipPath, CustomClipper
05:15 - LoginForm
07:05 - GradientButton
09:26 - LoginForm
10:03 - Reigister Screen
11:10 - Firebase Auth, Flutter Bloc, Equatable
11:48 - Repository 
13:08 - AuthenticationState
14:02 - AuthenticationEvent
14:27 - AuthenticationBloc
16:45 - LoginBloc Start
17:08 - LoginState
18:58 - LoginEvent
19:48 - LoginBloc
21:52 - Validators
22:11 - BlocObserver
23:37 - Authentication
24:47 - Login
30:50 - RegisterState, RegisterState, RegisterBloc
32:35 - Register

#fluter #firebase 

How to Create Login and Register Using Flutter Firebase Auth with Bloc

Flutter plugin for Twilio Programmable Video

twilio_programmable_video .Flutter plugin for Twilio Programmable Video, which enables you to build real-time videocall applications (WebRTC) 
This Flutter plugin is a community-maintained project for Twilio Programmable Video and not maintained by Twilio. If you have any issues, please file an issue instead of contacting support.

This package is currently work-in-progress and should not be used for production apps. We can't guarantee that the current API implementation will stay the same between versions, until we have reached v1.0.0.

Example

Check out our comprehensive example provided with this plugin.

Twilio Programmable Video Example

Join the community

If you have any question or problems, please join us on Discord

FAQ

Read the Frequently Asked Questions first before creating a new issue.

Supported platforms

  • Android
  • iOS
  • ~~Web~~ (not yet)

Getting started

Prerequisites

Before you can start using the plugin you need to make sure you have everything setup for your project.

Android

For this plugin to work for Android, you will have to tweak a few files.

Permissions

Open the AndroidManifest.xml file in your android/app/src/main directory and add the following device permissions:

...
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
...

Proguard

Add the following lines to your android/app/proguard-rules.pro file.

-keep class tvi.webrtc.** { *; }
-keep class com.twilio.video.** { *; }
-keep class com.twilio.common.** { *; }
-keepattributes InnerClasses

Also do not forget to reference this proguard-rules.pro in your android/app/build.gradle file.

android {

    ...

    buildTypes {

        release {

            ...    

            minifyEnabled true
            useProguard true

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }
    }
}

iOS

For this plugin to work for iOS, you will have to tweak a few files.

Permissions

Open the Info.plist file in your ios/Runner directory and add the following permissions:

...
<key>NSCameraUsageDescription</key>
<string>Your message to user when the camera is accessed for the first time</string>
<key>NSMicrophoneUsageDescription</key>
<string>Your message to user when the microphone is accessed for the first time</string>
<key>io.flutter.embedded_views_preview</key>
<true/>
...

Setting minimal iOS target to 11

  1. In Xcode, open Runner.xcworkspace in your app's ios folder.
  2. To view your app’s settings, select the Runner project in the Xcode project navigator. Then, in the main view sidebar, select the Runner target.
  3. Select the General tab.
  4. In the Deployment Info section, set the Target to iOS 11.

Background Modes

To allow a connection to a Room to be persisted while an application is running in the background, you must select the Audio, AirPlay, and Picture in Picture background mode from the Capabilities project settings page. See Twilio Docs for more information.

Connect to a Room

Call TwilioProgrammableVideo.connect() to connect to a Room in your Flutter application. Once connected, you can send and receive audio and video streams with other Participants who are connected to the Room.

Room _room;
final Completer<Room> _completer = Completer<Room>();

void _onConnected(Room room) {
  print('Connected to ${room.name}');
  _completer.complete(_room);
}

void _onConnectFailure(RoomConnectFailureEvent event) {
  print('Failed to connect to room ${event.room.name} with exception: ${event.exception}');
  _completer.completeError(event.exception);
}
  
Future<Room> connectToRoom() async {
  var connectOptions = ConnectOptions(
    accessToken,
    roomName: roomName,                   // Optional name for the room
    region: region,                       // Optional region.
    preferredAudioCodecs: [OpusCodec()],  // Optional list of preferred AudioCodecs
    preferredVideoCodecs: [H264Codec()],  // Optional list of preferred VideoCodecs.
    audioTracks: [LocalAudioTrack(true)], // Optional list of audio tracks.
    dataTracks: [
      LocalDataTrack(
        DataTrackOptions(
          ordered: ordered,                      // Optional, Ordered transmission of messages. Default is `true`.
          maxPacketLifeTime: maxPacketLifeTime,  // Optional, Maximum retransmit time in milliseconds. Default is [DataTrackOptions.defaultMaxPacketLifeTime]
          maxRetransmits: maxRetransmits,        // Optional, Maximum number of retransmitted messages. Default is [DataTrackOptions.defaultMaxRetransmits]
          name: name                             // Optional
        ),                                // Optional
      ),
    ],                                    // Optional list of data tracks   
    videoTracks: ([LocalVideoTrack(true, CameraCapturer(CameraSource.FRONT_CAMERA))]), // Optional list of video tracks.
  );
  _room = await TwilioProgrammableVideo.connect(connectOptions);
  _room.onConnected.listen(_onConnected);
  _room.onConnectFailure.listen(_onConnectFailure);
  return _completer.future;
}

You must pass the Access Token when connecting to a Room.

Join a Room

If you'd like to join a Room you know already exists, you handle that exactly the same way as creating a room: just pass the Room name to the connect method. Once in a Room, you'll receive a RoomParticipantConnectedEvent for each Participant that successfully joins. Querying the room.remoteParticipants getter will return any existing Participants who have already joined the Room.

Room _room;
final Completer<Room> _completer = Completer<Room>();

void _onConnected(Room room) {
  print('Connected to ${room.name}');
  _completer.complete(_room);
}

void _onConnectFailure(RoomConnectFailureEvent event) {
  print('Failed to connect to room ${event.room.name} with exception: ${event.exception}');
  _completer.completeError(event.exception);
}
  
Future<Room> connectToRoom() async {
  var connectOptions = ConnectOptions(
    accessToken,
    roomName: roomName,
    region: region,                       // Optional region.
    preferAudioCodecs: [OpusCodec()],     // Optional list of preferred AudioCodecs
    preferVideoCodecs: [H264Codec()],     // Optional list of preferred VideoCodecs.
    audioTracks: [LocalAudioTrack(true)], // Optional list of audio tracks.
    dataTracks: [
      LocalDataTrack(
        DataTrackOptions(
          ordered: ordered,                      // Optional, Ordered transmission of messages. Default is `true`.
          maxPacketLifeTime: maxPacketLifeTime,  // Optional, Maximum retransmit time in milliseconds. Default is [DataTrackOptions.defaultMaxPacketLifeTime]
          maxRetransmits: maxRetransmits,        // Optional, Maximum number of retransmitted messages. Default is [DataTrackOptions.defaultMaxRetransmits]
          name: name                             // Optional
        ),                                // Optional
      ),
    ],                                    // Optional list of data tracks
    videoTracks([LocalVideoTrack(true, CameraCapturer(CameraSource.FRONT_CAMERA))]), // Optional list of video tracks. 
  );
  _room = await TwilioProgrammableVideo.connect(connectOptions);
  _room.onConnected.listen(_onConnected);
  _room.onConnectFailure.listen(_onConnectFailure);
  return _completer.future;
}

Set up local media

You can capture local media from your device's microphone or camera in the following ways:

// Create an audio track.
var localAudioTrack = LocalAudioTrack(true);

// A video track request an implementation of VideoCapturer.
var cameraCapturer = CameraCapturer(CameraSource.FRONT_CAMERA);

// Create a video track.
var localVideoTrack = LocalVideoTrack(true, cameraCapturer);

// Getting the local video track widget.
// This can only be called after the TwilioProgrammableVideo.connect() is called.
var widget = localVideoTrack.widget();

Connect as a publish-only Participant

It is currently not possible to connect as a publish-only participant.

Working with Remote Participants

Handle Connected Participants

When you join a Room, Participants may already be present. You can check for existing Participants when the Room.onConnected listener gets called by using the room.remoteParticipants getter.

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onConnected((Room room) {
  print('Connected to ${room.name}');
});

room.onConnectFailure((RoomConnectFailureEvent event) {
    print('Failed connecting, exception: ${event.exception.message}');
});

room.onDisconnected((RoomDisconnectEvent event) {
  print('Disconnected from ${event.room.name}');
});

room.onRecordingStarted((Room room) {
  print('Recording started in ${room.name}');
});

room.onRecordingStopped((Room room) {
  print('Recording stopped in ${room.name}');
});

// ... Assume we have received the connected callback.

// After receiving the connected callback the LocalParticipant becomes available.
var localParticipant = room.localParticipant;
print('LocalParticipant ${room.localParticipant.identity}');

// Get the first participant from the room.
var remoteParticipant = room.remoteParticipants[0];
print('RemoteParticipant ${remoteParticipant.identity} is in the room');

Handle Participant Connection Events

When Participants connect to or disconnect from a Room that you're connected to, you'll be notified via an event listener. These events help your application keep track of the participants who join or leave a Room.

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onParticipantConnected((RoomParticipantConnectedEvent event) {
  print('Participant ${event.remoteParticipant.identity} has joined the room');
});

room.onParticipantDisconnected((RoomParticipantDisconnectedEvent event) {
  print('Participant ${event.remoteParticipant.identity} has left the room');
});

Display a Remote Participant's Widget

To see the Video Tracks being sent by remote Participants, we need to add their widgets to the tree.

room.onParticipantConnected((RoomParticipantConnectedEvent roomEvent) {
  // We can respond when the Participant adds a VideoTrack by adding the widget to the tree.
  roomEvent.remoteParticipant.onVideoTrackSubscribed((RemoteVideoTrackSubscriptionEvent event) {
    var mirror = false;
    _widgets.add(event.remoteParticipant.widget(mirror));
  });
});

Using the DataTrack API

The DataTrack API lets you create a DataTrack channel which can be used to send low latency messages to zero or more receivers subscribed to the data.

Currently the only way you can start using a DataTrack is by specifying it in the ConnectOptions when connecting to a room

After you have connected to the Room, you have to wait until you receive the LocalDataTrackPublishedEvent before you can start sending data to the track. You can start listening for this event once you have connected to the room using the Room.onConnected listener:

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onConnected((Room room) {
  // Once connected to the room start listening for the moment the LocalDataTrack gets published to the room.
  room.localParticipant.onDataTrackPublished.listen(_onLocalDataTrackPublished);
});

  // Once connected to the room start listening for the moment the LocalDataTrack gets published to the room.
  event.room.localParticipant.onDataTrackPublished.listen(_onLocalDataTrackPublished);
});

void _onLocalDataTrackPublished(LocalDataTrackPublishedEvent event) {
  // This event contains a localDataTrack you can use to send data.
  event.localDataTrackPublication.localDataTrack.send('Hello world');
}

If you want to receive data from a RemoteDataTrack you have to start listening to the track once the RemoteParticipant has started publishing it and you are subscribed to it:

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onParticipantConnected((RoomParticipantConnectedEvent event) {
  // A participant connected, now you can start listening to RemoteParticipant events
  event.remoteParticipant.onDataTrackSubscribed.listen(_onDataTrackSubscribed)
});

void _onDataTrackSubscribed(RemoteDataTrackSubscriptionEvent event) {
  final dataTrack = event.remoteDataTrackPublication.remoteDataTrack;
  dataTrack.onMessage.listen(_onMessage);
}

void _onMessage(RemoteDataTrackStringMessageEvent event) {
  print('onMessage => ${event.remoteDataTrack.sid}, ${event.message}');
}

Remember, you will not receive messages that were send before you started listening.

Participating in a Room

Display a Camera Preview

Just like Twilio we totally get that you want to look fantastic before entering a Room. Sadly that isn't yet implemented so you should go analog and use a mirror.

Disconnect from a Room

You can disconnect from a Room you're currently participating in. Other Participants will receive a RoomParticipantDisconnectedEvent.

// To disconnect from a Room, we call:
await room.disconnect();

// This results in a call to Room#onDisconnected
room.onDisconnected((RoomDisconnectEvent event) {
  print('Disconnected from ${event.room.name}');
});

Room reconnection

A Room reconnection is triggered due to a signaling or media reconnection event.

/// Exception will be either TwilioException.signalingConnectionDisconnectedException or TwilioException.mediaConnectionErrorException
room.onReconnecting((RoomReconnectingEvent event) {
  print('Reconnecting to room ${event.room.name}, exception = ${event.exception.message}');
});

room.onReconnected((Room room) {
  print('Reconnected to room ${room.name}');
});

Configuring Audio, Video Input and Output devices

Taking advantage of the ability to control input and output devices lets you build a better end user experience.

Selecting a specific Video Input

The CameraCapturer class is used to provide video frames for LocalVideoTrack from a given CameraSource.

// Share your camera.
var cameraCapturer = CameraCapturer(CameraSource.FRONT_CAMERA);
var localVideoTrack = LocalVideoTrack(true, cameraCapturer);

// Render camera to a widget (only after connect event).
var mirror = true;
var widget = localVideoTrack.widget(mirror);
_widgets.add(widget);

// Switch the camera source.
var cameraSource = cameraCapturer.getCameraSource();
cameraCapturer.switchCamera();
primaryVideoView.setMirror(cameraSource == CameraSource.BACK_CAMERA);

Selecting a specific Audio output

Using the TwilioProgrammableVideo class, you can specify if audio is routed through the headset or speaker.

Note:

Calling this method before being connected to a room on iOS will result in nothing. If you wish to route audio through the headset or speaker call this method in the onConnected event.

// Route audio through speaker
TwilioProgrammableVideo.setSpeakerphoneOn(true);

// Route audio through headset
TwilioProgrammableVideo.setSpeakerphoneOn(false);

Playing audio files to provide a rich user experience

For the purposes of playing audio files while using this plugin, we recommend the ocarina plugin (v0.0.5 and upwards).

This recommendation comes after surveying the available plugins for this functionality in the Flutter ecosystem for plugins that play nice with this one.

The primary problem observed with other plugins that provide this functionality is that on iOS the majority of them modify the AVAudioSession mode, putting it into a playback only mode, and as a result preventing the video call from recording audio.

The secondary problem with audio file playback in iOS is that the operating system gives priority to the VoiceProcessingIO Audio Unit, causing other audio sources to be played at a greatly diminished volume when this AudioUnit is in use. To address this issue, we provide the custom AVAudioEngineDevice which users of the plugin may enable with the example that follows. AVAudioEngineDevice was designed with ocarina in mind, providing an interface for delegating audio file playback and management from that plugin to the AVAudioEngineDevice. It was adapted from Twilio's example.

To enable usage of the AVAudioEngineDevice, and delegate audio file playback management from ocarina to it, update your AppDelegate.swifts didFinishLaunch method as follows:

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    let audioDevice = AVAudioEngineDevice()
    SwiftTwilioProgrammableVideoPlugin.audioDevice = audioDevice
    SwiftOcarinaPlugin.useDelegate(
        load: audioDevice.addMusicNode,
        dispose: audioDevice.disposeMusicNode,
        play: audioDevice.playMusic,
        pause: audioDevice.pauseMusic,
        resume: audioDevice.resumeMusic,
        stop: audioDevice.stopMusic,
        volume: audioDevice.setMusicVolume,
        seek: audioDevice.seekPosition,
        position: audioDevice.getPosition
    )

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

Once you have done this, you should be able to continue using this plugin, and ocarina as normal.

Enable debug logging

Using the TwilioProgrammableVideo class, you can enable native and dart logging of the plugin.

var nativeEnabled = true;
var dartEnabled = true;
TwilioProgrammableVideo.debug(native: nativeEnabled, dart: dartEnabled);

Access Tokens

Keep in mind, you can't generate access tokens for programmable-video using the TestCredentials, make use of the LIVE credentials.

You can easily generate an access token in the Twilio dashboard with the Testing Tools to start testing your code. But we recommend you setup a backend to generate these tokens for you and secure your Twilio credentials. Like we do in our example app.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add twilio_programmable_video

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  twilio_programmable_video: ^0.9.0+2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:twilio_programmable_video/twilio_programmable_video.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:twilio_programmable_video_example/debug.dart';
import 'package:twilio_programmable_video_example/room/join_room_page.dart';
import 'package:twilio_programmable_video_example/shared/services/backend_service.dart';
import 'package:firebase_core/firebase_core.dart';

void main() {
  Debug.enabled = true;
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations(<DeviceOrientation>[
    DeviceOrientation.landscapeRight,
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  runApp(TwilioProgrammableVideoExample());
}

class TwilioProgrammableVideoExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Firebase.initializeApp(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          return Provider<BackendService>(
            create: (_) => FirebaseFunctionsService.instance,
            child: MaterialApp(
              title: 'Twilio Programmable Video',
              theme: ThemeData(
                primarySwatch: Colors.blue,
                appBarTheme: AppBarTheme(
                  color: Colors.blue,
                  textTheme: TextTheme(
                    headline6: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.w500,
                      color: Colors.white,
                    ),
                  ),
                ),
              ),
              home: JoinRoomPage(),
            ),
          );
        } else {
          return Center(child: CircularProgressIndicator());
        }
      },
    );
  }
}

#fluter  #dart #mobile-apps

 

Flutter plugin for Twilio Programmable Video

WebEngage Flutter Plugin

WebEngage Flutter SDK

For more information checkout our website and documentation.

Installation

Add WebEngage Flutter Plugin

  • Add webengage_flutter in your pubspec.yaml file.
dependencies:
webengage_flutter: 1.0.3
  • Run flutter packages get to install the SDK

Initialization

Android

  1. Initialize WebEngage in main.dart in initState();
WebEngagePlugin _webEngagePlugin = new WebEngagePlugin();
  1. Initialize WebEngage Android SDK in your <your-project>/android/app/src/main/java/<your-package-path>/MainApplication.java class.
...
import com.webengage.sdk.android.WebEngageActivityLifeCycleCallbacks;
import io.flutter.app.FlutterApplication;

public class MainApplication extends FlutterApplication {
    @Override
    public void onCreate() {
        super.onCreate();
         WebEngageConfig webEngageConfig = new WebEngageConfig.Builder()
                .setWebEngageKey("YOUR_LICENCSE_CODE")
                .setAutoGCMRegistrationFlag(false)
                .setLocationTrackingStrategy(LocationTrackingStrategy.ACCURACY_BEST)
                .setDebugMode(true) // only in development mode
                .build();
        WebengageInitializer.initialize(this,webEngageConfig);
        ...
    }
    ...
}

Push Notifications

  1. Add below dependencies in app-level build gradle
    implementation platform('com.google.firebase:firebase-bom:25.12.0')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-messaging:20.2.1'
    implementation 'com.google.android.gms:play-services-ads:15.0.1'
  1. Add the following to your dependencies section in project/build.gradle
        classpath 'com.google.gms:google-services:4.3.4'
  1. Firebase tokens can be passed to WebEngage using FirebaseMessagingService
import com.google.firebase.messaging.FirebaseMessagingService;
import com.webengage.sdk.android.WebEngage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    @Override
    public void onNewToken(String s) {
        super.onNewToken(s);
        WebEngage.get().setRegistrationID(s);
    }
}

It is also recommended that you pass Firebase token to WebEngage from onCreate of your Application class as shown below. This will ensure that changes in user’s Firebase token are communicated to WebEngage.

import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import com.webengage.sdk.android.WebEngage;

public class MainApplication extends FlutterApplication {
    @Override
    public void onCreate() {
        super.onCreate();
    FirebaseMessaging.getInstance().getToken()
    .addOnCompleteListener(new OnCompleteListener<String>() {
        @Override
        public void onComplete(@NonNull Task<String> task) {
          if (!task.isSuccessful()) {
            Log.w(TAG, "Fetching FCM registration token failed", task.getException());
            return;
          }
          // Get new FCM registration token
          String token = task.getResult();
          WebEngage.get().setRegistrationID(token);
        }
    });
     
    }
}
  1. Pass Messages to WebEngage Create a class that extends FirebaseMessagingService and pass messages to WebEngage. All incoming messages from WebEngage will contain key source with the value as webengage.
package your.application.package;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.webengage.sdk.android.WebEngage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {
  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {
    Map<String, String> data = remoteMessage.getData();
    if(data != null) {
      if(data.containsKey("source") && "webengage".equals(data.get("source"))) {
        WebEngage.get().receive(data);
      }
    }
  }
}

Next, register the service to the application element of your AndroidManifest.xml as follows.

<service
    android:name=".MyFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

iOS

  1. Add WebEngage configurations <your-project>/ios/<YourApp>/Info.plist file.
<dict>
	<key>WEGLicenseCode</key>
	<string>YOUR-WEBENGAGE-LICENSE-CODE</string>

	<key>WEGLogLevel</key>
	<string>VERBOSE</string>
    ...
</dict>
  1. Initialize WebEngage iOS SDK in <your-project>/ios/<YourApp>/AppDelegate.m file.
#import <WebEngage/WebEngage.h>
...

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary * launchOptions {
    ...
  
    [[WebEngage sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
    
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

Push Notifications

Push Notification Callbacks

  1. Add Below code in AppDelegate.h file
  #import <WebEngagePlugin.h>
  
  @property (nonatomic, strong) WebEngagePlugin *bridge;
  1. Add Below code in AppDelegate.m file
    self.bridge = [WebEngagePlugin new];
    //For setting push click callback set pushNotificationDelegate after webengage SDK is initialised
    
    [[WebEngage sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions notificationDelegate:self.bridge];
    [WebEngage sharedInstance].pushNotificationDelegate = self.bridge;
  1. Add below subscribeToPushCallbacks() method in main.dart and call it from initMethod()
  void subscribeToPushCallbacks() {
      //Push click stream listener
      _webEngagePlugin.pushStream.listen((event) {
        String deepLink = event.deepLink;
        Map<String, dynamic> messagePayload = event.payload;
      });

      //Push action click listener
      _webEngagePlugin.pushActionStream.listen((event) {
        print("pushActionStream:" + event.toString());
        String deepLink = event.deepLink;
        Map<String, dynamic> messagePayload = event.payload;
      });
  }
  1. Add below code in dispose() of the main.dart
  //Close the streams in dispose()
  @override
  void dispose() {
    _webEngagePlugin.pushSink.close();
    _webEngagePlugin.pushActionSink.close();
    super.dispose();
  }

Universal Link

  1. Add Below code in AppDelegate.m file
  - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
  [[[WebEngage sharedInstance] deeplinkManager] getAndTrackDeeplink:userActivity.webpageURL callbackBlock:^(id location){
    [self.bridge trackDeeplinkURLCallback:location];
  }];
  return YES;
}
  1. Add below subscribeToTrackUniversalLink() method in main.dart and call it from initMethod()
 void subscribeToTrackUniversalLink() {
    _webEngagePlugin.trackDeeplinkStream.listen((location) {
      print("trackDeeplinkStream: " + location);
    });
  }
  1. Add below code in dispose() of the main.dart
  //Close the streams in dispose()
  @override
  void dispose() {
    _webEngagePlugin.trackDeeplinkURLStreamSink.close();
    super.dispose();
  }

Track Users

import 'package:webengage_flutter/webengage_flutter.dart';
...
    // User login
    WebEngagePlugin.userLogin('3254');

    // User logout
    WebEngagePlugin.userLogout();

    // Set user first name
    WebEngagePlugin.setUserFirstName('John');

    // Set user last name
    WebEngagePlugin.setUserLastName('Doe');

    // Set user email
    WebEngagePlugin.setUserEmail('john.doe@gmail.com');

    // Set user hashed email
    WebEngagePlugin.setUserHashedEmail('144e0424883546e07dcd727057fd3b62');

    // Set user phone number
    WebEngagePlugin.setUserPhone('+551155256325');

    // Set user hashed phone number
    WebEngagePlugin.setUserHashedPhone('e0ec043b3f9e198ec09041687e4d4e8d');

    // Set user company
    WebEngagePlugin.setUserCompany('WebEngage');

    // Set user birth-date, supported format: 'yyyy-MM-dd'
    WebEngagePlugin.setUserBirthDate('1994-05-24');

    // Set user gender, allowed values are ['male', 'female', 'other']
    WebEngagePlugin.setUserGender('male');

    // Set user channel opt-in status
    WebEngagePlugin.setUserOptIn('in_app', false);

    // Set user location
    WebEngagePlugin.setUserLocation(19.25, 72.45);

    // Set User Attribute with  String value
    WebEngagePlugin.setUserAttribute("twitterusename", "saurav12994");

    // Set User Attribute with  Boolean value
    WebEngagePlugin.setUserAttribute("Subscribed to email", true);

    // Set User Attribute with  Integer value
    WebEngagePlugin.setUserAttribute("Points earned", 2626);

    // Set User Attribute with  Double value
    WebEngagePlugin.setUserAttribute("Dollar Spent", 123.44);

    // Set User Attribute with  Map value
    var details = {'Usrname':'tom','Passiword':'pass@123'};
    WebEngagePlugin.setUserAttributes(details);

Track Events

import 'package:webengage_flutter/webengage_flutter.dart';
...
    // Track simple event
      WebEngagePlugin.trackEvent('Added to Cart');

      // Track event with attributes
      WebEngagePlugin.trackEvent('Order Placed', {'Amount': 808.48});

In-app Notifications

Track Screens

import 'package:webengage_flutter/webengage_flutter.dart';
...
    // Track screen
    WebEngagePlugin.trackScreen('Home Page');

    // Track screen with data
    WebEngagePlugin.trackScreen('Product Page', {'Product Id': 'UHUH799'});

In-app Notification Callbacks

  1. Add Below code in AppDelegate.h file
  #import <WebEngagePlugin.h>
  
  @property (nonatomic, strong) WebEngagePlugin *bridge;
  1. Add Below code in AppDelegate.m file
    self.bridge = [WebEngagePlugin new];
    //For setting in-app click callback set notificationDelegate while initialising WebEngage SDK
    
    [[WebEngage sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions notificationDelegate:self.bridge];
  1. Add Below Method in main.dart
 void _onInAppPrepared(Map<String, dynamic> message) {
    print("This is a inapp Prepated callback from native to flutter. Payload " +
        message.toString());
  }
  void _onInAppClick(Map<String, dynamic> message,String s) {
    print("This is a inapp click callback from native to flutter. Payload " +
        message.toString());

  }

  void _onInAppShown(Map<String, dynamic> message) {
    print("This is a callback on inapp shown from native to flutter. Payload " +
        message.toString());
  }

  void _onInAppDismiss(Map<String, dynamic> message) {
    print("This is a callback on inapp dismiss from native to flutter. Payload " +
        message.toString());
  }
  1. Add Below code inside initmethod() in main.dart
_webEngagePlugin.setUpInAppCallbacks(
        _onInAppClick, _onInAppShown, _onInAppDismiss, _onInAppPrepared);

More Info

Questions?

Reach out to our Support Team for further assistance.

Plugin info

WebEngage Flutter SDK

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add webengage_flutter

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  webengage_flutter: ^1.0.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:webengage_flutter/webengage_flutter.dart';

#fluter  #dart #mobile-apps

WebEngage Flutter Plugin

Lightweight Dart Web Server

Stream is a Dart web server supporting request routing, filtering, template engine, WebSocket, MVC design pattern and file-based static resources.

Stream is distributed under an Apache 2.0 License.

Installation

Add this to your pubspec.yaml (or create it):

dependencies:
  stream:

Usage

  • Introduction
  • Getting Started with Hello World

Compile RSP (Rikulo Stream Page) to dart files

There are two ways to compile RSP files into dart files: automatic building with Dart Editor or manual compiling.

RSP is a template technology allowing developers to create dynamically generated web pages based on HTML, XML or other document types (such as this and this).

Build with Dart Editor

To compile your RSP files automatically, you just need to add a build.dart file in the root directory of your project, with the following content:

import 'package:stream/rspc.dart';
void main(List<String> arguments) {
  build(arguments);
}

With this build.dart script, whenever your RSP is modified, it will be re-compiled.

Compile Manually

To compile a RSP file manually, run rspc (RSP compiler) to compile it into the dart file with command line interface as follows:

dart -c lib/rspc.dart -n dir1 dir2 file1 fire2...

A dart file is generated for each RSP file you gave. Fore more options, please run:

dart -c lib/rspc.dart -h

Notes to Contributors

Fork Stream

If you'd like to contribute back to the core, you can fork this repository and send us a pull request, when it is ready.

Please be aware that one of Stream's design goals is to keep the sphere of API as neat and consistency as possible. Strong enhancement always demands greater consensus.

If you are new to Git or GitHub, please read this guide first.

Who Uses

  • Quire - a simple, collaborative, multi-level task management tool.
  • Keikai - a sophisticated spreadsheet for big data

Use this package as an executable

1. Install it

You can install the package from the command line:

$ dart pub global activate stream

Use it

The package has the following executables:

$ rspc

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add stream

With Flutter:

 $ flutter pub add stream

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  stream: ^3.0.1

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:stream/stream.dart';

#fluter  #dart #mobile-apps

Lightweight Dart Web Server

Syncfusion Flutter Treemap Library

Flutter Treemap library A Flutter Treemap library for creating interactive treemap to visualize flat and hierarchical data as rectangles that are sized and colored based on quantitative variables using squarified, slice, and dice algorithms.

Overview

Create a highly interactive and customizable Flutter Treemap that has features set like selection, legends, labels, tooltips, color mapping, and much more.

Disclaimer: This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Free Syncfusion Community license. For more details, please check the LICENSE file.

Treemap features

Layouts - Use different layouts based on the algorithms such as squarified, slice, and dice to represent flat and hierarchically structured data.

  • Squarified:

Squarified layout

  • Slice:

Slice layout

  • Dice:

Dice layout

Hierarchical support - Along with the flat level, treemap supports hierarchical structure too. Each tile of the treemap is a rectangle which is filled with smaller rectangles representing sub-data.

Hierarchical support

Labels - Add any type of widgets (like text widget) to improve the readability of the individual tiles by providing brief descriptions on labels.

Selection - Allows you to select the tiles to highlight it and do any specific functionalities like showing pop-up or navigate to a different page.

Selection support

Legend - Use different legend styles to provide information on the treemap data clearly.

Legend support

Colors - Categorize the tiles on the treemap by customizing their color based on the levels. It is possible to set the tile color for a specific value or for a range of values.

Color customization

Tooltip - Display additional information about the tile using a completely customizable tooltip on the treemap.

Tooltip support

Drilldown - Drilldown the larger set of hierarchical level data for better visualization.

Sorting - Sort the tiles in a ascending or descending order.

Layout direction - Layout the tiles in all different corners of the rectangle. The possible layout directions are topLeft, topRight, bottomLeft, and bottomRight.

PointerOnLegend - Show a pointer at the top of the bar gradient legend while hovering on the tiles.

Custom background widgets - Add any type of custom widgets such as image widget as a background of the tiles to enrich the UI and easily visualize the type of data that a particular tile shows.

Treemap customization

Get the demo application

Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores. View sample codes in GitHub.

Useful links

Take a look at the following to learn more about Syncfusion Flutter Treemap:

Installation

Install the latest version from pub.

Flutter Treemap example

Import the following package.

import 'package:syncfusion_flutter_treemap/treemap.dart';

Create Treemap

After importing the package, initialize the treemap widget as a child of any widget.

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: SfTreemap(),
    ),
  );
}

Mapping the data source

To populate the data source, set its count to the dataCount property of the treemap. The data will be grouped based on the values returned from the TreemapLevel.groupMapper callback. You can have more than one TreemapLevel in the treemap levels collection to form a hierarchical treemap. The quantitative value of the underlying data has to be returned from the weightValueMapper callback. Based on this value, every tile (rectangle) will have its size.

late List<SocialMediaUsers> _source;

@override
void initState() {
  _source = <SocialMediaUsers>[
    SocialMediaUsers('India', 'Facebook', 25.4),
    SocialMediaUsers('USA', 'Instagram', 19.11),
    SocialMediaUsers('Japan', 'Facebook', 13.3),
    SocialMediaUsers('Germany', 'Instagram', 10.65),
    SocialMediaUsers('France', 'Twitter', 7.54),
    SocialMediaUsers('UK', 'Instagram', 4.93),
  ];
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfTreemap(
      dataCount: _source.length,
      weightValueMapper: (int index) {
        return _source[index].usersInMillions;
      },
      levels: [
        TreemapLevel(
          groupMapper: (int index) {
            return _source[index].country;
          },
        ),
      ],
    ),
  );
}

class SocialMediaUsers {
  const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions);

  final String country;
  final String socialMedia;
  final double usersInMillions;
}

Default treemap view

Add treemap elements

Add the basic treemap elements such as tooltip, labels, and legend as shown in the below code snippet.

Tooltip - You can enable tooltip for any tile in the treemap and able to return the completely customized widget using the tooltipBuilder property.

Labels - You can add any type of custom widgets to the tiles as labels based on the index using the labelBuilder property.

Legend - You can show legend by initializing the legend property in the SfTreemap. It is possible to customize the legend item's color and text using the SfTreemap.colorMappers property.

late List<SocialMediaUsers> _source;

@override
void initState() {
  _source = <SocialMediaUsers>[
    SocialMediaUsers('India', 'Facebook', 25.4),
    SocialMediaUsers('USA', 'Instagram', 19.11),
    SocialMediaUsers('Japan', 'Facebook', 13.3),
    SocialMediaUsers('Germany', 'Instagram', 10.65),
    SocialMediaUsers('France', 'Twitter', 7.54),
    SocialMediaUsers('UK', 'Instagram', 4.93),
  ];
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfTreemap(
      dataCount: _source.length,
      weightValueMapper: (int index) {
        return _source[index].usersInMillions;
      },
      levels: [
        TreemapLevel(
          groupMapper: (int index) {
            return _source[index].country;
          },
          labelBuilder: (BuildContext context, TreemapTile tile) {
            return Padding(
              padding: const EdgeInsets.all(2.5),
              child: Text(
                '${tile.group}',
                style: TextStyle(color: Colors.black),
              ),
            );
          },
          tooltipBuilder: (BuildContext context, TreemapTile tile) {
            return Padding(
              padding: const EdgeInsets.all(10),
              child: Text(
                  'Country          : ${tile.group}\nSocial media : ${tile.weight}M',
                  style: TextStyle(color: Colors.black)),
            );
          },
        ),
      ],
    ),
  );
}

class SocialMediaUsers {
  const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions);

  final String country;
  final String socialMedia;
  final double usersInMillions;
}

The following screenshot illustrates the result of the above code sample.

Treemap getting started

Support and feedback

About Syncfusion

Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 20,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies.

Today we provide 1,000+ controls and frameworks for web (ASP.NET Core, ASP.NET MVC, ASP.NET WebForms, JavaScript, Angular, React, Vue, and Blazor), mobile (Xamarin, Flutter, UWP, and JavaScript), and desktop development (WinForms, WPF, and UWP). We provide ready-to-deploy enterprise software for dashboards, reports, data integration, and big data processing. Many customers have saved millions in licensing fees by deploying our software.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add syncfusion_flutter_treemap

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  syncfusion_flutter_treemap: ^19.2.55-beta

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:syncfusion_flutter_treemap/treemap.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_treemap/treemap.dart';

void main() {
  return runApp(TreemapApp());
}

/// This widget will be the root of application.
class TreemapApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Treemap Demo',
      home: MyHomePage(),
    );
  }
}

/// This widget is the home page of the application.
class MyHomePage extends StatefulWidget {
  /// Initialize the instance of the [MyHomePage] class.
  const MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<SocialMediaUsers> _source;

  @override
  void initState() {
    _source = const <SocialMediaUsers>[
      SocialMediaUsers('India', 'Facebook', 25.4),
      SocialMediaUsers('USA', 'Instagram', 19.11),
      SocialMediaUsers('Japan', 'Facebook', 13.3),
      SocialMediaUsers('Germany', 'Instagram', 10.65),
      SocialMediaUsers('France', 'Twitter', 7.54),
      SocialMediaUsers('UK', 'Instagram', 4.93),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Treemap demo')),
      body: SfTreemap(
        dataCount: _source.length,
        weightValueMapper: (int index) {
          return _source[index].usersInMillions;
        },
        levels: <TreemapLevel>[
          TreemapLevel(
            groupMapper: (int index) {
              return _source[index].country;
            },
            labelBuilder: (BuildContext context, TreemapTile tile) {
              return Padding(
                padding: const EdgeInsets.all(2.5),
                child: Text(
                  tile.group,
                  style: const TextStyle(color: Colors.black),
                ),
              );
            },
            tooltipBuilder: (BuildContext context, TreemapTile tile) {
              return Padding(
                padding: const EdgeInsets.all(10),
                child: Text(
                    '''Country          : ${tile.group}\nSocial media : ${tile.weight}M''',
                    style: const TextStyle(color: Colors.black)),
              );
            },
          ),
        ],
      ),
    );
  }
}

/// Represents the class for social media users.
class SocialMediaUsers {
  /// Constructor of [SocialMediaUsers].
  const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions);

  /// Specifies the country.
  final String country;

  /// Specifies the type of social media.
  final String socialMedia;

  /// Specifies the users count.
  final double usersInMillions;
}

#fluter  #dart #mobile-apps

 Syncfusion Flutter Treemap Library

CloudKit Support for Flutter Via CloudKit Web Services

cloudkit_flutter .CloudKit support for Flutter via CloudKit Web Services.

Support

Currently, this library only supports Android (and iOS, although its usefulness there is fairly limited). The lack of Flutter Web support is due to one of the dependencies, webview_flutter, not supporting the Flutter Web platform 🙄.

Setup

Within your app, there are two stages involved in setting up this library. First, you must initialize the API manager with your CloudKit container, environment, and API token. Second, you must create your model classes based on the record types in CloudKit.

API Initialization

Before calls to the CloudKit API can be made, three values must be provided to the CKAPIManager:

  • CloudKit Container: The container ID used by CloudKit, which is typically iCloud. + your bundle ID.
  • CloudKit API Token: A token which must be created via the CloudKit dashboard. Importantly, you must select the last option ('cloudkit-' + container id + '://') within 'URL Redirect' for the 'Sign in Callback'. The custom URL can be any short string, such as 'redirect'.
  • CloudKit Environment: Changes whether the production or development environment is used. Corresponding values are provided as constants in the CKEnvironment class.

To initialize the manager, these three values must be passed into CKAPIManager.initManager(String container, String apiToken, CKEnvironment environment) async. This call should preferably be done in conjunction with the reflection setup, as described below.

Model Classes - Annotation

In this library, model classes must be annotated and then scanned so that reflection can be used to seamlessly convert JSON CloudKit records to a local Dart object.

There are three main types of annotations used in model files:

  • @CKRecordTypeAnnotation: to denote the name of the record type on CloudKit, and placed before the class declaration
  • @CKRecordNameAnnotation: to label the field within the local class where the CloudKit record name (a UUID) is stored
  • @CKFieldAnnotation: to associate fields in the local Dart object with record fields in CloudKit

Additionally, for the class to be scanned via reflection, you must tag the class with @reflector before the class declaration.

Below is an example of these annotations being used in a Dart file:

import 'package:cloudkit_flutter/cloudkit_flutter_model.dart';

@reflector
@CKRecordTypeAnnotation("Schedule")  // The name of the CloudKit record type is included in the annotation
class Schedule
{
  @CKRecordNameAnnotation() // No CloudKit record field name is needed as the field is always 'recordName'
  String? uuid;
  
  @CKFieldAnnotation("scheduleCode") // The name of the CloudKit record field is included in the annotation
  String? code;
  
  @CKFieldAnnotation("periodTimes")
  List<String>? blockTimes;
  
  @CKFieldAnnotation("periodNumbers")
  List<int>? blockNumbers;
}

Model Classes - Supported Field Types

Currently, most of the field types supported in CloudKit can be used in local model classes.

Many are fairly basic:

  • String
  • int
  • double
  • DateTime
  • List<String>
  • List<int>

There are a couple that require some explanation:

  • CKReference / List<CKReference>: The reference field type in CloudKit is used to create relations between two record types. The CKReference class has been created to represent this relation. To fetch the object associated with the reference, simply call the fetchFromCloud<T>() function, providing the corresponding local type (in place of T) when doing so.
  • CKAsset: The asset field type in CloudKit allows for the storage of literal bytes of data as a discrete asset. One common use for this type is to store an image. The CKAsset class has been created to represent this type, and it likewise has a fetchAsset() function to retrieve and cache the stored bytes. It also includes a getAsImage() function to convert the cached bytes to an image, if possible.
  • Subclasses of CKCustomFieldType: See below.

*More base field types will be added in later versions

Model Classes - Custom Field Types

Sometimes, a field within a CloudKit database only stores a raw value, to be later converted into an enum or more fully defined class when it reaches an app. To allow for custom classes to be used as types within model classes, the CKCustomFieldType class has been created.

There are several requirements for a subclass of CKCustomFieldType:

  • The class itself must provide a raw value type within the class declaration
  • There must be a default constructor which calls super.fromRecordField(T rawValue)
  • There must be a fromRecordField(T rawValue) constructor
  • The class must be tagged with @reflector, similar to the model classes

Below is a basic example of a custom field type class, Gender, which has int as its raw value type:

import 'package:cloudkit_flutter/cloudkit_flutter_model.dart';

@reflector
class Gender extends CKCustomFieldType<int>
{
  // Static instances of Gender with a raw value and name
  static final female = Gender.withName(0, "Female");
  static final male = Gender.withName(1, "Male");
  static final other = Gender.withName(2, "Other");
  static final unknown = Gender.withName(3, "Unknown");
  static final genders = [female, male, other, unknown];
  
  String name;
  
  // Required constructors
  Gender() : name = unknown.name, super.fromRecordField(unknown.rawValue);
  Gender.fromRecordField(int raw) : name = genders[raw].name, super.fromRecordField(raw);
  
  // Used to create static instances above
  Gender.withName(int raw, String name) : name = name, super.fromRecordField(raw);
  
  // The default toString() for CKCustomFieldType outputs the rawValue, but here it makes more sense to output the name
  @override
  String toString() => name;
}

Model Classes - Reflection Setup

Whenever you make changes to your model classes or CKCustomFieldType subclasses, you must regenerate object code to allow for reflection to be used within the library. First, ensure that the build_runner package is installed in your app's pubspec, as it is required to run the following command. Next, generate the object code by running flutter pub run build_runner build lib from the root folder of your Flutter project.

After the code has been generated, call initializeReflectable() (found within generated *.reflectable.dart files) at the start of your app before any other library calls are made. Finally, you must indicate to the CKRecordParser class which model classes should be scanned. To do this, call the CKRecordParser.createRecordStructures(List<Type>) function, listing the direct names of the local model classes within the list. To scan the Schedule class for example, we would call CKRecordParser.createRecordStructures([Schedule]). This call should preferably be done in conjunction with the API Initialization, as described above.

Usage

The main way to access the CloudKit API is through CKOperation, which is run though the execute() function. There are multiple kinds of operations, which are described below.

Operations

On creation, all operations require a string argument for the database (public, shared, private) to be used for the request. Optionally, a specific instance of a CKAPIManager can be passed in, although the shared instance is used by default. Additionally, a BuildContext can be optionally passed into the operation, in the off-chance that an iCloud sign-in view is necessary.

CKCurrentUserOperation

This operation fetches the CloudKit ID of the current user. It is also the simplest way to test if the user is signed in to iCloud, which is necessary to access the private database. Hence, the operation can be called at app launch or via a button to initiate the iCloud sign-in prompt.

Besides the default arguments for an operation as described above, this operation does not require any additional arguments.

Returned from the execute() call is the CloudKit ID of the signed-in user as a string.

CKRecordQueryOperation

This operation is the main method to retrieve records from CloudKit.

When creating the operation, you must pass in a local type for the operation to receive. For example: CKRecordQueryOperation<Schedule>(CKDatabase.PUBLIC_DATABASE) would fetch all Schedule records from the public database. Optionally, you can pass in a specific CKZone (zoneID), a List<CKFilter> (filters), or a List<CKSortDescriptor (sortDescriptors) to organize the results. You can also pass in a bool (preloadAssets) to indicate whether any CKAsset fields in fetched records should be preloaded.

Returned from the execute() call is an array of local objects with the type provided to the operation.

*More operations will be added in later versions

Request Models

In addition to the multiple kinds of operations, CloudKit provides several request parameters within its API, represented in this library by the classes below.

CKFilter

Filters are created through four main values: the name of the CloudKit record field to compare (fieldName), the CKFieldType of that record field (fieldType), the value to be compared against (fieldValue), and the CKComparator object for the desired comparison.

CKSortDescriptor

Sort descriptors are created through two main values: the name of the CloudKit record field to sort by (fieldName) and a boolean to indicate the direction (ascending).

CKZone

Zone objects are currently only containers for a zone ID string (zoneName), and can be used to specify a specific CloudKit zone for an operation. A zone object with an empty zone name will be set to the default zone.

CKQuery

Query objects are containers to store the CloudKit record type (recordType), a List<CKFilter> (filterBy), and a List<CKSortDescriptor> (sortBy).

CKRecordQueryRequest

Record query request objects represent the information needed to perform a CKRecordQueryOperation, including a CKZone (zoneID), a result limit (resultsLimit), and a CKQuery object (query).

Import points

To reduce the amount of included classes, you can choose to import a single section of the library, as described below.

cloudkit_flutter.dart

Includes all exposed classes.

cloudkit_flutter_init.dart

Includes classes necessary to initialize the API manager (CKAPIManager) and record parser (CKRecordParser).

cloudkit_flutter_model.dart

Includes classes necessary to annotate model files (CKRecordTypeAnnotation, CKRecordNameAnnotation, CKFieldAnnotation), use special field types (CKReference, CKAsset), and create custom field types (CKCustomFieldType).

cloudkit_flutter_api.dart

Includes classes necessary to call the CloudKit API (CKOperation + subclasses, CKZone, CKFilter, CKSortDescriptor).

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add cloudkit_flutter

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  cloudkit_flutter: ^0.1.4

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:cloudkit_flutter/cloudkit_flutter.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:developer';

import 'package:cloudkit_flutter/cloudkit_flutter_init.dart';
import 'package:cloudkit_flutter/cloudkit_flutter_api.dart';

import 'model/schedule.dart';
import 'model/week_schedule.dart';
import 'model/user_schedule.dart';

import 'main.reflectable.dart'; // Import generated code.
// Run `flutter pub run build_runner build example` from the root directory to generate example.reflectable.dart code

void main() async
{
  await initializeCloudKit();
  runApp(CKTestApp());
}

// To run this example code, you must have a CloudKit container with the following structure (as can be inferred from model/user_schedule.dart):
// UserSchedule: {
//   periodNames: List<String>
//   profileImage: CKAsset
//   genderRaw: int
// }
//
// Once the container is created, enter the CloudKit container and API token (set up via the CloudKit dashboard & with the options specified in README.md) below:

Future<void> initializeCloudKit() async
{
  const String ckContainer = ""; // YOUR CloudKit CONTAINER NAME HERE
  const String ckAPIToken = ""; // YOUR CloudKit API TOKEN HERE
  const CKEnvironment ckEnvironment = CKEnvironment.DEVELOPMENT_ENVIRONMENT;

  initializeReflectable();

  CKRecordParser.createRecordStructures([
    Schedule,
    WeekSchedule,
    UserSchedule
  ]);

  await CKAPIManager.initManager(ckContainer, ckAPIToken, ckEnvironment);
}

class CKTestApp extends StatelessWidget
{
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context)
  {
    return MaterialApp(
      title: 'iCloud Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CKTestPage(title: "iCloud Test"),
    );
  }
}

class CKTestPage extends StatefulWidget
{
  CKTestPage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _CKTestPageState createState() => _CKTestPageState();
}

class _CKTestPageState extends State<CKTestPage>
{
  CKSignInState isSignedIn = CKSignInState.NOT_SIGNED_IN;
  String currentUserOutput = "Get current user ID (and check if signed in)";
  String userScheduleOutput = "Fetch user schedule";

  void getCurrentUserCallback(CKSignInState isSignedIn, String currentUserOutput)
  {
    setState(() {
      this.isSignedIn = isSignedIn;
      this.currentUserOutput = currentUserOutput;
    });
  }

  void getUserScheduleCallback(String schedulesOutput)
  {
    setState(() {
      this.userScheduleOutput = schedulesOutput;
    });
  }

  @override
  Widget build(BuildContext context)
  {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          children: [
            Text(currentUserOutput),
            CKSignInButton(isSignedIn: isSignedIn, callback: getCurrentUserCallback),
            Padding(padding: EdgeInsets.all(8.0)),
            Text(userScheduleOutput),
            FetchUserScheduleTestButton(isSignedIn: isSignedIn, callback: getUserScheduleCallback),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}

class CKSignInButton extends StatefulWidget
{
  final Function(CKSignInState, String) callback;
  final CKSignInState isSignedIn;

  CKSignInButton({Key? key, required this.isSignedIn, required this.callback}) : super(key: key);

  @override
  State<StatefulWidget> createState() => CKSignInButtonState();
}

enum CKSignInState
{
  NOT_SIGNED_IN,
  SIGNING_IN,
  RE_SIGNING_IN,
  IS_SIGNED_IN
}

class CKSignInButtonState extends State<CKSignInButton>
{
  IconData getIconForCurrentState()
  {
    switch (widget.isSignedIn)
    {
      case CKSignInState.NOT_SIGNED_IN:
        return Icons.check_box_outline_blank;
      case CKSignInState.SIGNING_IN:
        return Icons.indeterminate_check_box_outlined;
      case CKSignInState.RE_SIGNING_IN:
        return Icons.indeterminate_check_box;
      case CKSignInState.IS_SIGNED_IN:
        return Icons.check_box;
    }
  }

  @override
  Widget build(BuildContext context)
  {
    return ElevatedButton(
        onPressed: () async {
          if (widget.isSignedIn == CKSignInState.IS_SIGNED_IN)
          {
            widget.callback(CKSignInState.RE_SIGNING_IN, "Re-signing in...");
          }
          else
          {
            widget.callback(CKSignInState.SIGNING_IN, "Signing in...");
          }

          var getCurrentUserOperation = CKCurrentUserOperation(CKDatabase.PUBLIC_DATABASE, context: context);
          var operationCallback = await getCurrentUserOperation.execute();

          switch (operationCallback.state)
          {
            case CKOperationState.success:
              var currentUserID = operationCallback.response as String;
              widget.callback(CKSignInState.IS_SIGNED_IN, currentUserID);
              break;

            case CKOperationState.authFailure:
              widget.callback(CKSignInState.NOT_SIGNED_IN, "Authentication failure");
              break;

            case CKOperationState.unknownError:
              widget.callback(CKSignInState.NOT_SIGNED_IN, "Unknown error");
              break;
          }
        },
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text("Sign In with iCloud"),
            Padding(padding: EdgeInsets.all(4.0)),
            Icon(getIconForCurrentState())
          ],
        )
    );
  }
}

class FetchUserScheduleTestButton extends StatefulWidget
{
  final Function(String) callback;
  final CKSignInState isSignedIn;

  FetchUserScheduleTestButton({Key? key, required this.isSignedIn, required this.callback}) : super(key: key);

  @override
  State<StatefulWidget> createState() => FetchUserScheduleTestButtonState();
}

class FetchUserScheduleTestButtonState extends State<FetchUserScheduleTestButton>
{
  @override
  Widget build(BuildContext context)
  {
    return ElevatedButton(
        onPressed: () async {
          if (widget.isSignedIn != CKSignInState.IS_SIGNED_IN)
          {
            widget.callback("Catch: Not signed in");
            return;
          }

          var queryPeopleOperation = CKRecordQueryOperation<UserSchedule>(CKDatabase.PRIVATE_DATABASE, preloadAssets: true, context: context);
          CKOperationCallback queryCallback = await queryPeopleOperation.execute();

          List<UserSchedule> userSchedules = [];
          if (queryCallback.state == CKOperationState.success) userSchedules = queryCallback.response;

          switch (queryCallback.state)
          {
            case CKOperationState.success:
              if (userSchedules.length > 0)
              {
                testUserSchedule(userSchedules[0]);
                widget.callback("Success");
              }
              else
              {
                widget.callback("No UserSchedule records");
              }
              break;

            case CKOperationState.authFailure:
              widget.callback("Authentication failure");
              break;

            case CKOperationState.unknownError:
              widget.callback("Unknown error");
              break;
          }
        },
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text("Fetch UserSchedules"),
          ],
        )
    );
  }
}

void testUserSchedule(UserSchedule userSchedule) async
{
  log(userSchedule.toString());

  // These are the class names for each period in userSchedule, automatically converted from CloudKit to the local object
  var periodNames = userSchedule.periodNames ?? [];
  log(periodNames.toString());

  // This is the data for a profile image, which can be casted (via .getAsImage()) due to `preloadAssets: true` when the operation was called
  var _ = (userSchedule.profileImage?.getAsImage() ?? AssetImage("assets/generic-user.png")) as ImageProvider;
  // If `preloadAssets: false`, the asset would have to be downloaded directly:
  await userSchedule.profileImage?.fetchAsset();
  log(userSchedule.profileImage?.size.toString() ?? 0.toString());

  // This is a custom `Gender` object, converted from a raw int form in CloudKit
  var gender = userSchedule.gender ?? Gender.unknown;
  log(gender.toString());
}

#fluter  #dart #mobile-apps

CloudKit Support for Flutter Via CloudKit Web Services

PostgreSQL Database Driver for Dart

PostgreSQL database driver for Dart A fork of Greg's PostgreSQL driver.

Differences

Array type with single dimension supported

The @@ operators supported.

DefaultTypeConverter.encodeValueDefault() assume all unknown types as JSON.

Pool.connect() throws an exception immediately if failed to connect to database.

More options to control the pool, such as limitTimeout and limitConnections

Basic usage

Obtaining a connection

var uri = 'postgres://username:password@localhost:5432/database';
connect(uri).then((conn) {
	// ...
});

SSL connections

Set the sslmode to require by appending this to the connection uri. This driver only supports sslmode=require, if sslmode is ommitted the driver will always connect without using SSL.

var uri = 'postgres://username:password@localhost:5432/database?sslmode=require';
connect(uri).then((conn) {
	// ...
});

Querying

conn.query('select color from crayons').toList().then((rows) {
	for (var row in rows) {
		print(row.color); // Refer to columns by name,
		print(row[0]);    // Or by column index.
	}
});

Executing

conn.execute("update crayons set color = 'pink'").then((rowsAffected) {
	print(rowsAffected);
});

Query Parameters

Query parameters can be provided using a map. Strings will be escaped to prevent SQL injection vulnerabilities.

conn.query('select color from crayons where id = @id', {'id': 5})
  .toList()
	.then((result) { print(result); });

conn.execute('insert into crayons values (@id, @color)',
             {'id': 1, 'color': 'pink'})
	.then((_) { print('done.'); });

Closing the connection

You must remember to call Connection.close() when you're done. This won't be done automatically for you.

Conversion of Postgresql datatypes.

Below is the mapping from Postgresql types to Dart types. All types which do not have an explicit mapping will be returned as a String in Postgresql's standard text format. This means that it is still possible to handle all types, as you can parse the string yourself.

     Postgresql type                 Dart type
	boolean                         bool
	int2, int4, int8                int
	float4, float8                  double
	numeric                         String
	timestamp, timestamptz, date    Datetime
	json, jsonb                     Map/List
	All other types                 String

Mapping the results of a query to an object

class Crayon {
	String color;
	int length;
}

conn.query('select color, length from crayons')
	.map((row) => new Crayon()
	                     ..color = row.color
	                     ..length = row.length)
	.toList()
	.then((List<Crayon> crayons) {
		for (var c in crayons) {
			print(c is Crayon);
			print(c.color);
			print(c.length);
		}
	});

Or for an immutable object:

class ImmutableCrayon {
	ImmutableCrayon(this.color, this.length);
	final String color;
	final int length;
}

conn.query('select color, length from crayons')
  .map((row) => new ImmutableCrayon(row.color, row.length))
     .toList()
	.then((List<ImmutableCrayon> crayons) {
		for (var c in crayons) {
			print(c is ImmutableCrayon);
			print(c.color);
			print(c.length);
		}
	});

Query queueing

Queries are queued and executed in the order in which they were queued.

So if you're not concerned about handling errors, you can write code like this:

conn.execute("create table crayons (color text, length int)");
conn.execute("insert into crayons values ('pink', 5)");
conn.query("select color from crayons").single.then((crayon) {
	print(crayon.color); // prints 'pink'
});

Query streaming

Connection.query() returns a Stream of results. You can use each row as soon as it is received, or you can wait till they all arrive by calling Stream.toList().

Connection pooling

In server applications, a connection pool can be used to avoid the overhead of obtaining a connection for each request.

import 'package:postgresql2/pool.dart';

main() {
  var uri = 'postgres://username:password@localhost:5432/database';
  var pool = new Pool(uri, minConnections: 2, maxConnections: 5);
  pool.messages.listen(print);
  pool.start().then((_) {
    print('Min connections established.');
    pool.connect().then((conn) { // Obtain connection from pool
      conn.query("select 'oi';")
        .toList()
        .then(print)
        .then((_) => conn.close()) // Return connection to pool
        .catchError((err) => print('Query error: $err'));
    });
  });
}

Example program

Add postgresql to your pubspec.yaml file, and run pub install.

name: postgresql_example
dependencies:
  postgresql2: any
import 'package:postgresql2/postgresql.dart';

void main() {
  var uri = 'postgres://testdb:password@localhost:5432/testdb';
  var sql = "select 'oi'"; 
  connect(uri).then((conn) {
    conn.query(sql).toList()
    	.then((result) {
    		print('result: $result');
    	})
    	.whenComplete(() {
    		conn.close();
    	});
  });
}

Testing

To run the unit tests you will need to create a database, and edit 'test/config.yaml' accordingly.

Creating a database for testing

Change to the postgres user and run the administration commands.

sudo su postgres
createuser --pwprompt testdb
  Enter password for new role: password
  Enter it again: password
  Shall the new role be a superuser? (y/n) n
  Shall the new role be allowed to create databases? (y/n) n
  Shall the new role be allowed to create more new roles? (y/n) n
createdb --owner testdb testdb
exit

Check that it worked by logging in.

psql -h localhost -U testdb -W

Enter "\q" to quit from the psql console.

Notes

Substitution of Array types

Like others, you can specify the array type in a substitution. For example,

insert into foo values(@name, @tags:array)

However, if the list can be empty, you have to specify the type explicitly since PostgreSQL cannot determine type of empty array. For example,

insert into foo values(@name, @tags:text_array)

It is equivalent to the following:

insert into foo values(@name, @tags:array::text[])

License

BSD

Utilities

  • Transaction management: https://github.com/rikulo/access
  • Entity management: https://github.com/rikulo/entity

Links

  • https://www.postgresql.org/docs/9.2/static/index.html
  • https://www.dartlang.org/

Who Uses

  • Quire - a simple, collaborative, multi-level task management tool.
  • Keikai - a sophisticated spreadsheet for big data

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add postgresql2

With Flutter:

 $ flutter pub add postgresql2

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  postgresql2: ^1.0.1

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:postgresql2/constants.dart';
import 'package:postgresql2/pool.dart';
import 'package:postgresql2/postgresql.dart';

#fluter  #dart #mobile-apps

PostgreSQL Database Driver for Dart

A Flutter Plugin for using the Firebase Auth UI with Dart in Flutter

flutter_auth_ui .A Flutter plugin for using the Firebase Auth UI with Dart in Flutter apps. (not official plugin.)

Status

  • Android
    • support
  • iOS
    • support
  • web
    • support

Link

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_auth_ui

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  flutter_auth_ui: ^2.3.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_auth_ui/flutter_auth_ui.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_auth_ui/flutter_auth_ui.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: [
              ElevatedButton(
                child: const Text("start ui"),
                onPressed: () async {
                  final providers = [
                    AuthUiProvider.anonymous,
                    AuthUiProvider.email,
                    AuthUiProvider.phone,
                    AuthUiProvider.apple,
                    AuthUiProvider.github,
                    AuthUiProvider.google,
                    AuthUiProvider.microsoft,
                    AuthUiProvider.yahoo,
                  ];

                  final result = await FlutterAuthUi.startUi(
                    items: providers,
                    tosAndPrivacyPolicy: TosAndPrivacyPolicy(
                      tosUrl: "https://www.google.com",
                      privacyPolicyUrl: "https://www.google.com",
                    ),
                    androidOption: AndroidOption(
                      enableSmartLock: false, // default true
                      showLogo: true, // default false
                      overrideTheme: true, // default false
                    ),
                    emailAuthOption: EmailAuthOption(
                      requireDisplayName: true, // default true
                      enableMailLink: false, // default false
                      handleURL: '',
                      androidPackageName: '',
                      androidMinimumVersion: '',
                    ),
                  );
                  print(result);
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

#fluter  #dart #mobile-apps

A Flutter Plugin for using the Firebase Auth UI with Dart in Flutter