1677153300
An example of a WebSocket Server created with Dart language and a client that can connect to it.
Open terminal in project directory
>> dart ./bin/server.dart
Example Output:
H:\AndroidStudioProjects\werbsocket_server>dart ./bin/server.dart
Listening on localhost:4040
Open terminal in project directory
>> dart ./bin/client.dart
Author: kerimbr
Source code: https://github.com/kerimbr/dart_websocket_example
1655587980
In this video tutorial, We'll show you How to Create and Publish Dart Packages With Step by Step for beginners.
#Dart #fluter
1655551751
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
1655035200
The Android implementation of the nsd
plugin.
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:
1654092000
Boustro is a rich text editor for Flutter.
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.
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.
Light | Dark |
---|---|
![]() | ![]() |
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.
Check out the docs or the example.
TextField
. However, this would greatly complicate the line paragraph system, and I'm not sure that's worth it.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:WidgetSpan
. Please go upvote these issues if you'd like to see these limitations overcome.The changelog documents all notable changes.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
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.
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
1647334800
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
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:
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"
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:
flutter pub run build_runner build
dart pub run build_runner build
Features
Let's start from simple login form.
First we need to define our form 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.
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.
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.
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]
.
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.
The model is pretty simple.
class MailingList {
final List<String?> emailList;
MailingList({
this.emailList = const [],
});
}
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.
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.
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,
);
},
),
],
)
],
);
},
);
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.
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,
});
}
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.
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.
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,
);
},
),
],
),
);
},
);
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.
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,
});
}
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.
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,
);
},
),
],
)
],
);
},
);
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
@freezed
class FreezedClass with _$FreezedClass {
const factory FreezedClass({
String? id,
String? name,
double? year,
}) = _FreezedClass;
factory FreezedClass.fromJson(Map<String, dynamic> json) =>
_$FreezedClassFromJson(json);
}
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);
}
You can add validations the same way as in basics example
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,
);
},
),
],
);
},
);
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,
});
}
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.
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
1632541370
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:
showModalBottomSheet
function#fluter
1629821018
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
1629091500
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.
If you have any question or problems, please join us on Discord
Read the Frequently Asked Questions first before creating a new issue.
Before you can start using the plugin you need to make sure you have everything setup for your project.
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'
}
}
}
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
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.
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.
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;
}
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();
It is currently not possible to connect as a publish-only participant.
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');
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');
});
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));
});
});
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.
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.
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}');
});
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}');
});
Taking advantage of the ability to control input and output devices lets you build a better end user experience.
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);
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);
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.
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);
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.
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.
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());
}
},
);
}
}
1629087600
WebEngage Flutter SDK
For more information checkout our website and documentation.
Add WebEngage Flutter Plugin
dependencies:
webengage_flutter: 1.0.3
WebEngagePlugin _webEngagePlugin = new WebEngagePlugin();
...
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);
...
}
...
}
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'
classpath 'com.google.gms:google-services:4.3.4'
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);
}
});
}
}
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>
<dict>
<key>WEGLicenseCode</key>
<string>YOUR-WEBENGAGE-LICENSE-CODE</string>
<key>WEGLogLevel</key>
<string>VERBOSE</string>
...
</dict>
#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 Notification Callbacks
#import <WebEngagePlugin.h>
@property (nonatomic, strong) WebEngagePlugin *bridge;
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;
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;
});
}
//Close the streams in dispose()
@override
void dispose() {
_webEngagePlugin.pushSink.close();
_webEngagePlugin.pushActionSink.close();
super.dispose();
}
Universal Link
- (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;
}
void subscribeToTrackUniversalLink() {
_webEngagePlugin.trackDeeplinkStream.listen((location) {
print("trackDeeplinkStream: " + location);
});
}
//Close the streams in dispose()
@override
void dispose() {
_webEngagePlugin.trackDeeplinkURLStreamSink.close();
super.dispose();
}
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);
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});
import 'package:webengage_flutter/webengage_flutter.dart';
...
// Track screen
WebEngagePlugin.trackScreen('Home Page');
// Track screen with data
WebEngagePlugin.trackScreen('Product Page', {'Product Id': 'UHUH799'});
#import <WebEngagePlugin.h>
@property (nonatomic, strong) WebEngagePlugin *bridge;
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];
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());
}
_webEngagePlugin.setUpInAppCallbacks(
_onInAppClick, _onInAppShown, _onInAppDismiss, _onInAppPrepared);
Reach out to our Support Team for further assistance.
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.
Now in your Dart code, you can use:
import 'package:webengage_flutter/webengage_flutter.dart';
1629042707
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.
Add this to your pubspec.yaml (or create it):
dependencies:
stream:
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).
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.
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
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.
You can install the package from the command line:
$ dart pub global activate stream
The package has the following executables:
$ rspc
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.
Now in your Dart code, you can use:
import 'package:stream/stream.dart';
1629042479
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.
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.
Layouts - Use different layouts based on the algorithms such as squarified, slice, and dice to represent flat and hierarchically structured data.
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.
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.
Legend - Use different legend styles to provide information on the treemap data clearly.
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.
Tooltip - Display additional information about the tile using a completely customizable tooltip on the treemap.
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.
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.
Take a look at the following to learn more about Syncfusion Flutter Treemap:
Install the latest version from pub.
Import the following package.
import 'package:syncfusion_flutter_treemap/treemap.dart';
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(),
),
);
}
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;
}
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.
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.
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.
Now in your Dart code, you can use:
import 'package:syncfusion_flutter_treemap/treemap.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;
}
1629042139
cloudkit_flutter .CloudKit support for Flutter via CloudKit Web Services.
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 🙄.
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.
Before calls to the CloudKit API can be made, three values must be provided to the CKAPIManager:
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.
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:
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;
}
Currently, most of the field types supported in CloudKit can be used in local model classes.
Many are fairly basic:
There are a couple that require some explanation:
*More base field types will be added in later versions
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:
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;
}
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.
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.
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.
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.
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
In addition to the multiple kinds of operations, CloudKit provides several request parameters within its API, represented in this library by the classes below.
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.
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).
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.
Query objects are containers to store the CloudKit record type (recordType), a List<CKFilter> (filterBy), and a List<CKSortDescriptor> (sortBy).
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).
To reduce the amount of included classes, you can choose to import a single section of the library, as described below.
Includes all exposed classes.
Includes classes necessary to initialize the API manager (CKAPIManager) and record parser (CKRecordParser).
Includes classes necessary to annotate model files (CKRecordTypeAnnotation, CKRecordNameAnnotation, CKFieldAnnotation), use special field types (CKReference, CKAsset), and create custom field types (CKCustomFieldType).
Includes classes necessary to call the CloudKit API (CKOperation + subclasses, CKZone, CKFilter, CKSortDescriptor).
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.
Now in your Dart code, you can use:
import 'package:cloudkit_flutter/cloudkit_flutter.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());
}
1629041595
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
var uri = 'postgres://username:password@localhost:5432/database';
connect(uri).then((conn) {
// ...
});
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) {
// ...
});
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.
}
});
conn.execute("update crayons set color = 'pink'").then((rowsAffected) {
print(rowsAffected);
});
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.'); });
You must remember to call Connection.close() when you're done. This won't be done automatically for you.
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
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);
}
});
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'
});
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().
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'));
});
});
}
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();
});
});
}
To run the unit tests you will need to create a database, and edit 'test/config.yaml' accordingly.
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.
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[])
BSD
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.
Now in your Dart code, you can use:
import 'package:postgresql2/constants.dart';
import 'package:postgresql2/pool.dart';
import 'package:postgresql2/postgresql.dart';
1629041089
flutter_auth_ui .A Flutter plugin for using the Firebase Auth UI with Dart in Flutter apps. (not official plugin.)
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.
Now in your Dart code, you can use:
import 'package:flutter_auth_ui/flutter_auth_ui.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);
},
),
],
),
),
),
);
}
}