Dart Library for Changing String Case Style to The Desired Conventio

dart_casing

Dart library for changing String case style to the desired convention.

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  dart_casing: <latest version>

Usage

import 'package:dart_casing/dart_casing.dart';

main()
{
  var text = "Lorem-ipsum_dolor\\SIT amet";

  print(Casing.camelCase(text));                   // loremIpsumDolorSitAmet
  print(Casing.pascalCase(text));                  // LoremIpsumDolorSitAmet
  print(Casing.titleCase(text));                   // Lorem Ipsum Dolor Sit Amet
  print(Casing.titleCase(text, separator: "_"));   // Lorem_Ipsum_Dolor_Sit_Amet
  print(Casing.snakeCase(text));                   // lorem_ipsum_dolor_sit_amet
  print(Casing.paramCase(text));                   // lorem-ipsum-dolor-sit-amet
  print(Casing.constantCase(text));                // LOREM_IPSUM_DOLOR_SIT_AMET
  print(Casing.lowerCase(text, separator: " "));   // lorem ipsum dolor sit amet
  print(Casing.upperCase(text, separator: " "));   // LOREM IPSUM DOLOR SIT AMET
}

Issues

Please file any issues, bugs or feature request here.

License

This project is licensed under the MIT License

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add dart_casing

With Flutter:

 $ flutter pub add dart_casing

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

dependencies:
  dart_casing: ^2.0.0

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

Import it

Now in your Dart code, you can use:

import 'package:dart_casing/dart_casing.dart'; 

example/dart_casing_example.dart

import 'package:dart_casing/dart_casing.dart';

main()
{
  var text = "Lorem-ipsum_dolor\\SIT amet";

  print(Casing.camelCase(text));                  // loremIpsumDolorSitAmet
  print(Casing.pascalCase(text));                 // LoremIpsumDolorSitAmet
  print(Casing.titleCase(text));                  // Lorem Ipsum Dolor Sit Amet
  print(Casing.titleCase(text, separator: "_"));  // Lorem_Ipsum_Dolor_Sit_Amet
  print(Casing.snakeCase(text));                  // lorem_ipsum_dolor_sit_amet
  print(Casing.paramCase(text));                  // lorem-ipsum-dolor-sit-amet
  print(Casing.constantCase(text));               // LOREM_IPSUM_DOLOR_SIT_AMET
  print(Casing.lowerCase(text, separator: " "));  // lorem ipsum dolor sit amet
  print(Casing.upperCase(text, separator: " "));  // LOREM IPSUM DOLOR SIT AMET
} 

Download Details:

Author: Jesway

Source Code: https://github.com/Jesway/Dart-Casing

#dart #android #ios 

What is GEEK

Buddha Community

Dart Library for Changing String Case Style to The Desired Conventio
Bulah  Pfeffer

Bulah Pfeffer

1648873833

A Collection of Flutter and Dart Tips and Tricks

Table of Contents

  • LazyStream in Flutter and Dart
  • Cancelable APIs in Flutter
  • Asset Data in Flutter
  • API Caching in Flutter
  • FutureGroup in Dart
  • Flatten Iterable<bool> in Dart
  • Caching Temp Files in Flutter
  • Custom Lists in Dart
  • Optional Chaining in Dart
  • MapList in Flutter
  • Future<bool> in Flutter
  • Async Bloc Init in Flutter
  • Firebase Auth Errors in Flutter
  • Debug Strings in Flutter
  • Keyboard Appearance in Flutter
  • Get String Data in Dart
  • Stream.startWith in Flutter
  • Optional Functions in Dart
  • AnnotatedRegion in Flutter
  • Unordered Map Equality in Dart
  • Iterable to ListView in Flutter
  • Password Mask in Flutter
  • Fast Object.toString() in Dart
  • Copying Bloc State in Flutter
  • Iterable Subscripts in Dart
  • useState in Flutter Hooks
  • Folding Iterables in Dart
  • Custom Iterables in Dart
  • Class Clusters in Dart
  • Iterable +/- in Dart
  • Periodic Streams in Dart
  • EmptyOnError in Dart
  • Stream<T> Initial Value in Flutter
  • Double.normalize in Dart
  • Hide Sensitive Information in Flutter
  • Iterable.compactMap in Dart
  • useEffect in Flutter Hooks
  • Merging Streams in Dart
  • Isolate Stream in Dart
  • Network Image Retry in Flutter
  • Reusable APIs in Flutter
  • ListTile Shadow in Flutter
  • Transparent AppBar in Flutter
  • Constructors on Abstract Classes in Dart
  • @useResult in Dart
  • @mustCallSuper in Dart
  • Object.hash in Dart
  • Expanded Equally in Flutter
  • Random Iterable Value in Dart
  • Hardcoded Strings in Flutter
  • Manually Scroll in List View in Flutter
  • AsyncSnapshot to Widget in Flutter
  • Breadcrumbs in Flutter
  • Unique Map Values in Dart
  • Smart Quotes/Dashes in Flutter
  • Haptic Feedback in Flutter
  • Localization Delegates in Flutter
  • Extending Functions in Dart
  • Paginated ListView in Flutter
  • Immutable Classes in Dart
  • Card Widget in Flutter
  • List Equality Ignoring Ordering in Dart
  • Shorten GitHub URLs in Dart
  • Time Picker in Flutter
  • Throttled Print in Flutter
  • Map Equality in Dart
  • Unique Maps in Dart
  • Raw Auto Complete in Flutter
  • Title on Object in Dart
  • Compute in Flutter
  • Filter on Map in Dart
  • Type Alias in Dart
  • ValueNotifier in Flutter
  • Object to Integer in Dart
  • Image Opacity in Flutter
  • Covariant in Dart
  • Custom Errors in Streams in Dart
  • Shake Animation in Flutter
  • Throw Enums in Dart
  • Future Error Test in Flutter
  • Generic URL Retrieval in Dart
  • Custom Error Widget in Flutter
  • Handle Multiple Future Errors in Dart
  • Future Error Handling in Dart
  • String to Toast in Flutter
  • Waiting in Dart
  • Loading Dialog in Flutter
  • Compact Map on Map<K,V> in Dart
  • Query Parameters in Dart
  • Multiple Gradients in Container in Flutter
  • Filter on Stream<List<T>> in Dart
  • Generic Route Arguments in Flutter
  • Generic Dialog in Flutter
  • GitHub API in Flutter
  • ChangeNotifier in Flutter
  • Refresh Indicator in Flutter
  • FlatMap in Dart
  • OrientationBuilder in Flutter
  • Linear Gradient in Flutter
  • Bloc Text Editing Controller in Flutter
  • Blurred TabBar in Flutter
  • Play YouTube in Flutter
  • ListView Background in Flutter
  • Integer to Binary in Dart
  • Split String by Length in Dart
  • Image Tint in Flutter
  • SlideTransition in Flutter
  • Expansion Panels and Lists in Flutter
  • Complete CRUD App in Flutter
  • SQLite Storage in Flutter
  • Circular Progress with Percentage in Flutter
  • Opening URLs in Flutter
  • Commodore 64 Screen in Flutter
  • Animated Lists in Flutter
  • CheckboxListTile in Flutter
  • - Operator on String in Dart
  • Dart Progress for Future<T>
  • Move Widget Shadows with Animation
  • Gallery with Blurred Backgrounds in Flutter
  • Custom Path Clippers in Flutter
  • Frost Effect on Images in Flutter
  • Custom Clippers in Flutter
  • Check if Website is Up or Down in Dart
  • Section Titles on ListView in Flutter
  • Circular Progress in Flutter
  • Displaying Scroll Wheels in Flutter
  • Post Messages to Slack with Dart
  • Unwrap List<T?>? in Dart
  • Avoiding UI Jitters When Switching Widgets in Flutter
  • Detect Redirects in Dart
  • Proportional Constraints in Flutter
  • Displaying Cupertino Action Sheets in Flutter
  • Rotating List<T> in Dart
  • Displaying SnackBars in Flutter
  • Custom Tab Bar Using ToggleButtons in Flutter
  • Hashable Mixins in Dart
  • Flutter Tips and Tricks in Terminal
  • Searching List<List<T>> in Dart
  • Cloning Objects in Dart
  • Color Filters in Flutter
  • Flattening Lists in Dart
  • Managing Duplicates in List<T> in Dart
  • FlatMap and CompactMap in Dart
  • Equality of List<T> in Dart
  • Constants in Dart
  • Displaying Scrollable Bottom Sheets in Flutter
  • YouTube Ad Remover in Dart
  • Fade Between Widgets in Flutter
  • Sort Descriptors in Dart
  • User Sortable Columns and Tables in Flutter
  • Content-Length of List<Uri> in Dart
  • Recursive Dot Notation on Maps in Dart
  • Allow User Selection of Text in Flutter
  • Placing Constraints on Widgets in Flutter
  • Animating Position Changes in Flutter
  • Transitioning Between Widgets in Flutter
  • Doubly Linked Lists in Dart
  • Reordering Items Inside List Views in Flutter
  • Custom Stream Transformers in Dart
  • Expanding Stream Elements in Dart
  • Consume Streams for a Duration in Dart
  • Shortening URLs in Dart
  • LimitedBox Widget as ListView Items in Flutter
  • Generically Convert Anything to Int in Dart
  • Validating URL Certificates in Dart
  • Displaying Popup Menus in Flutter
  • Implementing Drag and Drop in Flutter
  • Dismissing List Items in Flutter
  • Animating Widgets with Ease in Flutter
  • Displaying Tool Tips in Flutter
  • Displaying Assorted Widgets Inside TableView in Flutter
  • Page Indicator with Page View in Flutter
  • Animating and Moving a Floating Action Button in Flutter
  • Fading Network Image Widget in Flutter
  • Transparent Alert Dialogs in Flutter
  • Network Image Size in Dart
  • Animated Icons in Flutter
  • Custom Scroll Views in Flutter
  • Parallax App Bar in Flutter
  • JSON HTTP Requests in Dart
  • URL Timeouts in Dart
  • Detecting URL File Types in Dart
  • Paginated Lists in Dart
  • Requesting DELETE on APIs in Dart
  • Animated Containers in Flutter
  • Hiding Widgets in Flutter
  • Simple Opacity Animation in Flutter
  • Vignette Widget in Flutter
  • Drop Down Button Configuration and Usage in Flutter
  • Expandable List Items in Flutter
  • Infinite Scrolling in Flutter
  • Infinite Arrays in Dart
  • Custom Color Picker Component in Flutter
  • Displaying and Reacting to Switches in Flutter
  • Displaying Bottom Bars in Flutter
  • Displaying Buttons on AppBar in Flutter
  • Displaying Bottom Sheets in Flutter
  • Converting Enums to Radio Buttons in Flutter
  • Check Existence of Websites in Flutter
  • Images inside AlertDialog in Flutter
  • Returning Values from AlertDialog in Flutter
  • Simple Grid View in Flutter
  • Rendering Bullet Points in Flutter
  • Retrying Futures in Flutter
  • Containers as ClipOvals in Flutter
  • Rich Texts in Flutter
  • Wrapping Widgets in Flutter
  • Sweep Gradients in Flutter
  • Stream and StreamBuilder in Flutter
  • Blur Effect in Flutter
  • Convert Enums to Strings in Dart
  • Replacing Text in TextField in Flutter
  • Aspect Ratio in Flutter
  • Zoom and Pan in Flutter
  • Resizing Images in Flutter to Fit Screen Height
  • Validating URLs in Flutter
  • FrameBuilder for Network Images in Flutter
  • Adding Shadow to Icons in Flutter
  • Calculating Median of Lists in Dart
  • Generic Functions with Reduce in Dart
  • Passing Back Data From a Screen to the Previous One in Flutter
  • Flinging an Animation in Flutter
  • Fade Animations in Flutter
  • Throttling User Input in Flutter
  • Censoring TextFields in Flutter
  • Customizing TextButton in Flutter
  • Multiline TextFields in Flutter
  • Filtering TextField Input in Flutter
  • Focusing Manually on TextFields in Flutter
  • Data Streams Over HTTP/HTTPs in Dart
  • Catching Nonexistent Accessors or Methods in Dart
  • Using Expando in Dart
  • Implementing Custom Maps in Dart
  • Dynamically Calling Functions in Dart
  • Factory Constructors in Dart
  • Calculating the Sum of List Items in Dart
  • Removing Duplicate Strings in Lists in Dart (Case-Insensitive)
  • Implementing Range in Dart
  • Converting Lists to Maps in Dart
  • Implementing Hashable in Dart
  • Random Name Generator in Dart
  • Capturing Stack Traces in Dart Exceptions
  • Removing Duplicates from Lists in Dart
  • Optional Spread Operator in Dart
  • Calling Optional Functions in Dart
  • Odd-Even Sort in Dart
  • Implementing Zip and Tuples in Dart
  • Swapping Values in Lists with XOR in Dart
  • Waiting for Multiple Futures in Dart
  • Using Queues as Stacks in Dart
  • Custom Iterators in Dart
  • Iterables as Ranges + Transform in Dart
  • Errors vs Exceptions in Dart
  • Custom Annotations in Dart
  • Classes as Enums in Dart
  • Spread Operator in Collection Literals in Dart
  • StreamBuilder and StreamController in Dart
  • Almost Equal in Dart
  • Enum Associated Values in Dart
  • Implementing Comparable in Dart
  • Implementing Custom Integer Types in Dart
  • Custom Subscripts in Dart
  • Dart List Enumeration with Index
  • Applying Mixins to Other Mixins in Dart
  • Parameter Types in Dart
  • Custom Exceptions in Dart
  • rethrowing Exceptions in Dart
  • mixins and JSON Parsing in Dart
  • mixins vs abstract classes in Dart
  • Drawing Shapes in Flutter with LayoutBuilder, CustomPaint and CustomPainter
  • Generic Type Aliases in Dart
  • Callable Classes in Dart
  • Synchronous Generators in Dart
  • Implicit Interfaces in Dart
  • const Constructors in Dart
  • async-await Over Raw Futures in Dart
  • Initializer List and Default Values as Convenience Intializers in Dart
  • Extract Elements of Certain Type from Lists in Dart
  • Type Promotion in Dart
  • Extract Minimum and Maximum Values in List<num> in Dart
  • Functions as First Class Citizens in Dart

 

LazyStream in Flutter and Dart



import 'dart:developer' as devtools show log;
import 'dart:typed_data' show Uint8List;
import 'package:flutter/services.dart' show NetworkAssetBundle, rootBundle;
import 'package:async/async.dart' show LazyStream;

extension LocalFileData on String {
  Future<Uint8List> localFileData() => rootBundle.load(this).then(
        (byteData) => byteData.buffer.asUint8List(),
      );
}

extension Log on Object {
  void log() => devtools.log(toString());
}

void testIt() async {
  final stream = LazyStream(
    () async {
      final allData = await calculateAllData();
      return getImagesData(allData);
    },
  );

  await for (final data in stream) {
    'Got data, length = ${data.length}'.log();
  }
}

Stream<Uint8List> getImagesData(
  List<Future<Uint8List>> allData,
) async* {
  for (final data in allData) {
    yield await data;
  }
}

Future<List<Future<Uint8List>>> calculateAllData() async {
  final futures = Iterable.generate(
    3,
    (i) => 'images/image_list${i + 1}.txt'
        .localFileData()
        .then((data) => String.fromCharCodes(data)),
  );
  final result = Future.wait(futures);
  final lineSplitter = const LineSplitter();
  List<Future<Uint8List>> allData = [];
  for (final string in await result) {
    final urls = lineSplitter.convert(string);
    for (final url in urls) {
      allData.add(
        NetworkAssetBundle(Uri.parse(url))
            .load(url)
            .then((byteData) => byteData.buffer.asUint8List()),
      );
    }
  }
  return allData;
}

Cancelable APIs in Flutter

import 'dart:developer' as devtools show log;
import 'dart:typed_data' show Uint8List;
import 'package:flutter/services.dart' show NetworkAssetBundle, rootBundle;
import 'package:async/async.dart' show CancelableOperation;

extension Log on Object {
  void log() => devtools.log(toString());
}

extension LocalFileData on String {
  Future<Uint8List> localFileData() => rootBundle.load(this).then(
        (byteData) => byteData.buffer.asUint8List(),
      );
}

CancelableOperation<Uint8List> getImageOperation(String url) =>
    CancelableOperation.fromFuture(
      NetworkAssetBundle(Uri.parse(url))
          .load(url)
          .then((byteData) => byteData.buffer.asUint8List()),
      onCancel: () => 'images/template.png'.localFileData(),
    );

void testIt() async {
  final operation = getImageOperation('http://127.0.0.1:5500/images/1.png');
  final cancelledValue = await operation.cancel();
  final result = await operation.valueOrCancellation(cancelledValue);
  result?.log();
}

Asset Data in Flutter


import 'dart:typed_data' show Uint8List;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

extension LocalFileData on String {
  Future<Uint8List> localFileData() => rootBundle.load(this).then(
        (byteData) => byteData.buffer.asUint8List(),
      );
}

void testIt() async {
  (await 'images/template.png'.localFileData()).log();
}

API Caching in Flutter


import 'dart:typed_data' show Uint8List;
import 'package:flutter/services.dart' show NetworkAssetBundle;
import 'dart:developer' as devtools show log;
import 'package:async/async.dart' show AsyncMemoizer;

extension Log on Object {
  void log() => devtools.log(toString());
}

@immutable
class GetImageApi {
  final String url;
  final _fetch = AsyncMemoizer<Uint8List>();

  GetImageApi({required this.url});
  Future<Uint8List> fetch() => _fetch.runOnce(
        () => NetworkAssetBundle(Uri.parse(url))
            .load(url)
            .then((byteData) => byteData.buffer.asUint8List()),
      );
}

void testIt() async {
  final api = GetImageApi(url: 'http://127.0.0.1:5500/images/1.png');
  (await api.fetch()).log(); // fetched
  (await api.fetch()).log(); // cached
}

FutureGroup in Dart



mixin FutureConvertible<T> {
  Future<T> toFuture();
}

@immutable
class LoginApi with FutureConvertible<bool> {
  @override
  Future<bool> toFuture() => Future.delayed(
        const Duration(seconds: 1),
        () => true,
      );
}

@immutable
class SignUpApi with FutureConvertible<bool> {
  @override
  Future<bool> toFuture() => Future.delayed(
        const Duration(seconds: 1),
        () => true,
      );
}

extension Flatten on Iterable<bool> {
  bool flatten() => fold(
        true,
        (lhs, rhs) => lhs && rhs,
      );
}

extension Log on Object {
  void log() => devtools.log(toString());
}

Future<bool> startup({
  required bool shouldLogin,
  required bool shouldSignUp,
}) {
  final group = FutureGroup<bool>();
  if (shouldLogin) {
    group.add(LoginApi().toFuture());
  }
  if (shouldSignUp) {
    group.add(SignUpApi().toFuture());
  }
  group.close();
  return group.future.then((bools) => bools.flatten());
}

void testIt() async {
  final success = await startup(
    shouldLogin: true,
    shouldSignUp: false,
  );
  success.log();
}

Flatten Iterable<bool> in Dart


extension Flatten on Iterable<bool> {
  bool flatten() => fold(
        true,
        (lhs, rhs) => lhs && rhs,
      );
}

void testIt() {
  assert([true, false, true].flatten() == false);
  assert([true, true, true].flatten() == true);
  assert([false, false, false].flatten() == false);
  assert([true].flatten() == true);
  assert([false].flatten() == false);
}

Caching Temp Files in Flutter



@immutable
class NetworkImageAsset {
  final String localPath;
  final String url;
  NetworkImageAsset({required int index})
      : localPath = Directory.systemTemp.path + '/$index.png',
        url = 'http://127.0.0.1:5500/images/$index}.png';

  Future<bool> downloadAndSave() => NetworkAssetBundle(Uri.parse(url))
      .load(url)
      .then((byteData) => byteData.buffer.asUint8List())
      .then((data) => File(localPath).writeAsBytes(data).then((_) => true))
      .catchError((_) => false);
}

void testIt() async {
  await Future.forEach(
    Iterable.generate(
      3,
      (i) => NetworkImageAsset(index: i + 1),
    ),
    (NetworkImageAsset asset) => asset.downloadAndSave(),
  );
}

Custom Lists in Dart



import 'dart:developer' as devtools show log;
import 'dart:collection' show ListBase;

class LowercaseList extends ListBase<String> {
  final List<String> _list = [];

  @override
  int get length => _list.length;
  @override
  set length(int newLength) => _list.length = newLength;

  @override
  String operator [](int index) => _list[index].toUpperCase();

  @override
  void operator []=(int index, value) => _list[index] = value;

  @override
  void addAll(Iterable<String> iterable) => _list.addAll(iterable);

  @override
  void add(String element) => _list.add(element);
}

extension Log on Object {
  void log() => devtools.log(toString());
}

void testIt() {
  final myList = LowercaseList();
  myList.addAll(['foo', 'bar', 'baz']);
  myList[0].log(); // FOO
  myList[1].log(); // BAR
  for (final item in myList) {
    item.log(); // FOO, BAR, BAZ
  }
}

Optional Chaining in Dart



@immutable
class Address {
  final String? firstLine;
  final String? secondLine;
  const Address(this.firstLine, this.secondLine);
}

@immutable
class Person {
  final Person? father;
  final Address? address;
  const Person(this.father, this.address);
}

extension GetFathersFirstAddressLine on Person {
  String? get firstAddressLineOfFather => father?.address?.firstLine;
}

MapList in Flutter



extension MapToList<T> on Iterable<T> {
  List<E> mapList<E>(E Function(T) toElement) => 
    map(toElement).toList();
}

Future<bool> in Flutter



Future<bool> uploadImage({
  required File file,
  required String userId,
}) =>
    FirebaseStorage.instance
        .ref(userId)
        .child(const Uuid().v4())
        .putFile(file)
        .then((_) => true)
        .catchError((_) => false);

Async Bloc Init in Flutter



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

  @override
  Widget build(BuildContext context) {
    return BlocProvider<AppBloc>(
      create: (context) => AppBloc()..add(const AppEventInitialize()),
      child: MaterialApp(
        title: 'Photo Library',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        debugShowCheckedModeBanner: false,
        home: BlocConsumer<AppBloc, AppState>(
          listener: (context, state) {
            // handle loading
            if (state.isLoading) {
              LoadingScreen().show(
                context: context,
                text: 'Loading...',
              );
            } else {
              LoadingScreen().hide();
            }
            
            ... rest of your code goes here

Firebase Auth Errors in Flutter



const authErrorMapping = {
  'user-not-found': AuthErrorUserNotFound(),
  'project-not-found': AuthErrorProjectNotFound(),
};

@immutable
abstract class AuthError {
  factory AuthError.from(FirebaseAuthException exception) =>
      authErrorMapping[exception.code.toLowerCase().trim()] ??
      const AuthErrorUnknown();
}

@immutable
class AuthErrorUnknown implements AuthError {
  const AuthErrorUnknown();
}

@immutable
class AuthErrorUserNotFound implements AuthError {
  const AuthErrorUserNotFound();
}

@immutable
class AuthErrorProjectNotFound implements AuthError {
  const AuthErrorProjectNotFound();
}

Debug Strings in Flutter


extension IfDebugging on String {
  String? get ifDebugging => kDebugMode ? this : null;
}

class LoginView extends HookWidget {
  const LoginView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final emailController = useTextEditingController(
      text: 'foo@bar.com'.ifDebugging,
    );
    final passwordController = useTextEditingController(
      text: 'foobarbaz'.ifDebugging,
    );
    
    // rest of your code would be here ...

Keyboard Appearance in Flutter


class LoginView extends HookWidget {
  const LoginView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Log in'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: const [
            TextField(
              keyboardType: TextInputType.emailAddress,
              keyboardAppearance: Brightness.dark,
            ),
            TextField(
              obscureText: true,
              obscuringCharacter: '◉',
            ),
          ],
        ),
      ),
    );
  }
}

Get String Data in Dart


extension ToList on String {
  Uint8List toUint8List() => Uint8List.fromList(codeUnits);
}

final text1Data = 'Foo'.toUint8List();
final text2Data = 'Bar'.toUint8List();

Stream.startWith in Flutter


import 'package:async/async.dart' show StreamGroup;
import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

extension StartWith<T> on Stream<T> {
  Stream<T> startWith(T value) => StreamGroup.merge([
        this,
        Stream<T>.value(value),
      ]);
}

void testIt() {
  Stream.periodic(const Duration(seconds: 1), (i) => i + 1)
      .startWith(0)
      .take(4)
      .forEach((element) {
    element.log();
  }); // 0, 1, 2, 3
}

Optional Functions in Dart



typedef AppBlocRandomUrlPicker = String Function(Iterable<String> allUrls);

extension RandomElement<T> on Iterable<T> {
  T getRandomElement() => elementAt(
        math.Random().nextInt(length),
      );
}

class AppBloc extends Bloc<AppEvent, AppState> {
  String _pickRandomUrl(Iterable<String> allUrls) => allUrls.getRandomElement();

  AppBloc({
    required Iterable<String> urls,
    AppBlocRandomUrlPicker? urlPicker,
  }) : super(const AppState.empty()) {
    on<LoadNextUrlEvent>(
      (event, emit) {
        emit(
          const AppState(
            isLoading: true,
            data: null,
          ),
        );
        // pick a random URL to load
        final url = (urlPicker ?? _pickRandomUrl)(urls);
        HttpClient().getUrl(Uri.parse(url)); // continue here...
      },
    );
  }
}

AnnotatedRegion in Flutter



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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnnotatedRegion<SystemUiOverlayStyle>(
        value: SystemUiOverlayStyle.dark,
        child: Column(
          mainAxisSize: MainAxisSize.max,
          children: [
            Expanded(child: Container(color: Colors.blue)),
            Expanded(child: Container(color: Colors.yellow)),
          ],
        ),
      ),
    );
  }
}

Unordered Map Equality in Dart



import 'package:collection/collection.dart';
import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

extension UnorderedEquality<K, V> on Map<K, V> {
  bool isEqualTo(Map<K, V> other) =>
      const DeepCollectionEquality.unordered().equals(this, other);
}

void testIt() {
  final dict1 = {
    'name': 'foo',
    'age': 20,
    'values': ['foo', 'bar'],
  };
  final dict2 = {
    'age': 20,
    'name': 'foo',
    'values': ['bar', 'foo'],
  };
  dict1.isEqualTo(dict2).log(); // true
}

Iterable to ListView in Flutter



extension ToListView<T> on Iterable<T> {
  Widget toListView() => IterableListView(
        iterable: this,
      );
}

class IterableListView<T> extends StatelessWidget {
  final Iterable<T> iterable;
  const IterableListView({
    Key? key,
    required this.iterable,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: iterable.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(
            iterable.elementAt(index).toString(),
          ),
        );
      },
    );
  }
}

@immutable
class Person {
  final String name;
  final int age;
  const Person({required this.name, required this.age});
  @override
  String toString() => '$name, $age years old';
}

const persons = [
  Person(name: 'Foo', age: 20),
  Person(name: 'Bar', age: 30),
  Person(name: 'Baz', age: 40),
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      body: persons.toListView(),
    );
  }
}

Password Mask in Flutter



class PasswordTextField extends StatelessWidget {
  const PasswordTextField({
    Key? key,
    required this.passwordController,
  }) : super(key: key);

  final TextEditingController passwordController;

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: passwordController,
      obscureText: true,
      obscuringCharacter: '◉',
      decoration: const InputDecoration(
        hintText: 'Enter your password here...',
      ),
    );
  }
}

Fast Object.toString() in Dart



@immutable
class AppState {
  final bool isLoading;
  final Object? loginError;
  final String? loginHandle;
  final Iterable<String>? fetchedNotes;

  @override
  String toString() => {
        'isLoading': isLoading,
        'loginError': loginError,
        'loginHandle': loginHandle,
        'fetchedNotes': fetchedNotes
      }.toString();

  const AppState({
    required this.isLoading,
    required this.loginError,
    required this.loginHandle,
    required this.fetchedNotes,
  });
}

Copying Bloc State in Flutter



@immutable
class AppState {
  final bool isLoading;
  final LoginHandle? loginHandle;
  final Iterable<Note>? fetchedNotes;

  const AppState.empty()
      : isLoading = false,
        loginHandle = null,
        fetchedNotes = null;

  const AppState({
    required this.isLoading,
    required this.loginHandle,
    required this.fetchedNotes,
  });

  AppState copiedWith({
    bool? isLoading,
    LoginHandle? loginHandle,
    Iterable<Note>? fetchedNotes,
  }) =>
      AppState(
        isLoading: isLoading ?? this.isLoading,
        loginHandle: loginHandle ?? this.loginHandle,
        fetchedNotes: fetchedNotes ?? this.fetchedNotes,
      );
}

Iterable Subscripts in Dart

// Free Flutter Course 💙 https://linktr.ee/vandadnp
// Want to support my work 🤝? https://buymeacoffee.com/vandad

import 'dart:developer' as devtools show log;

extension Log on Object? {
  void log() => devtools.log(toString());
}

extension Subscript<T> on Iterable<T> {
  T? operator [](int index) => length > index ? elementAt(index) : null;
}

void testIt() {
  Iterable.generate(10, (i) => i + 1)[0].log(); // 1
  Iterable.generate(1, (i) => i)[2].log(); // null
  Iterable.generate(10, (i) => i + 1)[9].log(); // 10
  Iterable.generate(0, (i) => i)[0].log(); // null
}

useState in Flutter Hooks



import 'package:flutter_hooks/flutter_hooks.dart';
import 'dart:math' show min;

@immutable
class VirtualTab {
  final Icon icon;
  final String text;

  const VirtualTab({
    required this.icon,
    required this.text,
  });
}

const tabs = [
  VirtualTab(
    icon: Icon(Icons.picture_as_pdf),
    text: 'All PDF files',
  ),
  VirtualTab(
    icon: Icon(Icons.ac_unit_outlined),
    text: 'Data page',
  ),
  VirtualTab(
    icon: Icon(Icons.person),
    text: 'Profile page',
  ),
];

class HomePage extends HookWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final tabCount = useState(1);
    return DefaultTabController(
      length: tabCount.value,
      initialIndex: tabCount.value - 1,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: tabs
                .take(tabCount.value)
                .map((tab) => Tab(icon: tab.icon))
                .toList(),
          ),
        ),
        body: CustomTabBarView(tabCount: tabCount),
      ),
    );
  }
}

class CustomTabBarView extends StatelessWidget {
  const CustomTabBarView({
    Key? key,
    required this.tabCount,
  }) : super(key: key);

  final ValueNotifier<int> tabCount;

  @override
  Widget build(BuildContext context) {
    return TabBarView(
      children: tabs
          .take(tabCount.value)
          .map(
            (tab) => Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                children: [
                  Text(tab.text),
                  TextButton(
                    onPressed: () {
                      final newLength = min(
                        tabs.length,
                        tabCount.value + 1,
                      );
                      tabCount.value = newLength;
                    },
                    child: const Text('Create next tab'),
                  )
                ],
              ),
            ),
          )
          .toList(),
    );
  }
}

 

Folding Iterables in Dart



import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

void testIt() {
  final values = ['foo', 'bar', 'baz', '1.0'];

  values.fold<int>(0, (pe, e) => pe + e.length); // 12

  values.fold<String>('', (pe, e) => '$pe$e'); // foobarbaz1.0

  values.fold<Map<String, int>>(
    {},
    (pe, e) => pe..addAll(<String, int>{e: e.length}),
  ).log(); // {foo: 3, bar: 3, baz: 3, 1.0: 3}

  values.fold<double>(
    0.0,
    (pe, e) => pe + (double.tryParse(e) ?? 0.0),
  ); // 1.0
}

Custom Iterables in Dart



class Address with IterableMixin {
  final String line1;
  final String line2;
  final String postCode;

  Address({
    required this.line1,
    required this.line2,
    required this.postCode,
  });

  @override
  Iterator<String> get iterator => [line1, line2, postCode].iterator;
}

void testIt() {
  final address = Address(
    line1: 'Foo bar avenue, #10',
    line2: 'Baz street',
    postCode: '123456',
  );
  for (final line in address) {
    devtools.log(line);
  }
}

Class Clusters in Dart



enum AnimalType { dog, cat }

@immutable
abstract class Animal {
  const Animal();
  factory Animal.fromType(AnimalType type) {
    switch (type) {
      case AnimalType.dog:
        return const Dog();
      case AnimalType.cat:
        return const Cat();
    }
  }
  void makeNoise();
}

@immutable
class Dog extends Animal {
  const Dog();
  @override
  void makeNoise() => 'Woof'.log();
}

@immutable
class Cat extends Animal {
  const Cat();
  @override
  void makeNoise() => 'Meow'.log();
}

void testIt() {
  final cat = Animal.fromType(AnimalType.cat);
  cat.makeNoise();
  final dog = Animal.fromType(AnimalType.dog);
  dog.makeNoise();
}

Iterable +/- in Dart



extension AddRemoveItems<T> on Iterable<T> {
  Iterable<T> operator +(T other) => followedBy([other]);
  Iterable<T> operator -(T other) => where((element) => element != other);
}

void testIt() {
  final values = ['foo', 'bar']
    .map((e) => e.toUpperCase()) + 'BAZ';
  values.log(); // (FOO, BAR, BAZ)
  (values - 'BAZ').log(); // (FOO, BAR)
}

Periodic Streams in Dart



import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

@immutable
class Person {
  final String name;
  final int age;

  const Person({
    required this.name,
    required this.age,
  });

  Person.fromJson(Map<String, dynamic> json)
      : name = json["name"] as String,
        age = json["age"] as int;

  @override
  String toString() => 'Person ($name, $age years old)';
}

mixin ListOfThingsAPI<T> {
  Future<Iterable<T>> get(String url) => HttpClient()
      .getUrl(Uri.parse(url))
      .then((req) => req.close())
      .then((resp) => resp.transform(utf8.decoder).join())
      .then((str) => json.decode(str) as List<dynamic>)
      .then((list) => list.cast());
}

class GetPeople with ListOfThingsAPI<Map<String, dynamic>> {
  Future<Iterable<Person>> getPeople(url) => get(url).then(
        (jsons) => jsons.map(
          (json) => Person.fromJson(json),
        ),
      );
}

Stream<dynamic> every(Duration duration) => Stream.periodic(duration);

extension IntToDuration on int {
  Duration get seconds => Duration(seconds: this);
}

void testIt() async {
  await for (final people in every(3.seconds).asyncExpand(
    (_) => GetPeople()
        .getPeople('http://127.0.0.1:5500/apis/people1.json')
        .asStream(),
  )) {
    people.log();
  }
}

/* people1.json
[
    {
        "name": "Foo 1",
        "age": 20
    },
    {
        "name": "Bar 1",
        "age": 30
    }
]
*/

EmptyOnError in Dart



import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

@immutable
class Person {
  final String name;
  final int age;

  const Person({
    required this.name,
    required this.age,
  });

  Person.fromJson(Map<String, dynamic> json)
      : name = json["name"] as String,
        age = json["age"] as int;

  @override
  String toString() => 'Person ($name, $age years old)';
}

const people1Url = 'http://127.0.0.1:5500/apis/people11.json';
const people2Url = 'http://127.0.0.1:5500/apis/people2.json';

extension EmptyOnError<E> on Future<List<Iterable<E>>> {
  Future<List<Iterable<E>>> emptyOnError() => catchError(
        (_, __) => List<Iterable<E>>.empty(),
      );
}

Future<Iterable<Person>> parseJson(String url) => HttpClient()
    .getUrl(Uri.parse(url))
    .then((req) => req.close())
    .then((resp) => resp.transform(utf8.decoder).join())
    .then((str) => json.decode(str) as List<dynamic>)
    .then((json) => json.map((e) => Person.fromJson(e)));

void testIt() async {
  final persons = await Future.wait([
    parseJson(people1Url),
    parseJson(people2Url),
  ]).emptyOnError();
  persons.log();
}

Stream<T> Initial Value in Flutter



import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const HomePage(),
    ),
  );
}

const url = 'https://bit.ly/3x7J5Qt';

class HomePage extends HookWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    late final StreamController<double> controller;
    controller = useStreamController<double>(onListen: () {
      controller.sink.add(0.0);
    });

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home page'),
      ),
      body: StreamBuilder<double>(
          stream: controller.stream,
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return const CircularProgressIndicator();
            } else {
              final rotation = snapshot.data ?? 0.0;
              return GestureDetector(
                onTap: () {
                  controller.sink.add(rotation + 10.0);
                },
                child: RotationTransition(
                  turns: AlwaysStoppedAnimation(rotation / 360.0),
                  child: Center(
                    child: Image.network(url),
                  ),
                ),
              );
            }
          }),
    );
  }
}

Double.normalize in Dart



import 'dart:developer' as devtools show log;

extension Normalize on double {
  double normalized(
    double selfRangeMin,
    double selfRangeMax, [
    double normalizedRangeMin = 0.0,
    double normalizedRangeMax = 1.0,
  ]) =>
      (normalizedRangeMax - normalizedRangeMin) *
          ((this - selfRangeMin) / (selfRangeMax - selfRangeMin)) +
      normalizedRangeMin;
}

extension Log on Object {
  void log() => devtools.log(toString());
}

void testIt() async {
  2.0.normalized(0, 2.0).log(); // 1.0
  4.0.normalized(0, 8.0).log(); // 0.5
  5.0.normalized(4.0, 6.0, 10.0, 20.0).log(); // 15
}

Hide Sensitive Information in Flutter

 



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

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const HomePage(),
    ),
  );
}

class HomePage extends HookWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final state = useAppLifecycleState();
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Opacity(
          opacity: state == AppLifecycleState.resumed ? 1.0 : 0.0,
          child: Container(
            decoration: BoxDecoration(
              boxShadow: [
                BoxShadow(
                  blurRadius: 10,
                  color: Colors.black.withAlpha(100),
                  spreadRadius: 10,
                ),
              ],
            ),
            child: Image.asset('assets/card.png'),
          ),
        ),
      ),
    );
  }
}

Iterable.compactMap in Dart



import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

extension CompactMap<T> on Iterable<T?> {
  Iterable<T> compactMap<E>([
    E? Function(T?)? transform,
  ]) =>
      map(transform ?? (e) => e).where((e) => e != null).cast();
}

const list = ['Hello', null, 'World'];

void testIt() {
  list.log(); // [Hello, null, World]
  list.compactMap().log(); // [Hello, World]
  list.compactMap((e) => e?.toUpperCase()).log(); // [HELLO, WORLD]
}

useEffect in Flutter Hooks



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

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const HomePage(),
    ),
  );
}

class HomePage extends HookWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = useTextEditingController();
    final text = useState('');
    useEffect(
      () {
        void listener() {
          text.value = controller.text;
        }

        controller.addListener(listener);
        return () => controller.removeListener(listener);
      },
      [controller],
    );
    return Scaffold(
      body: Column(
        children: [
          TextField(
            controller: controller,
          ),
          Text('You typed ${text.value}')
        ],
      ),
    );
  }
}

Merging Streams in Dart



import 'package:async/async.dart' show StreamGroup;
import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

void testIt() async {
  final streams = Iterable.generate(
    3,
    (i) => Stream.periodic(
      const Duration(seconds: 1),
      (_) => 'Stream $i: ${DateTime.now().toIso8601String()}',
    ).take(i + 1),
  );
  await for (final now in StreamGroup.merge(streams)) {
    now.log();
  }
}

Isolate Stream in Dart



Stream<String> getMessages() {
  final rp = ReceivePort();
  return Isolate.spawn(_getMessages, rp.sendPort)
      .asStream()
      .asyncExpand((_) => rp)
      .takeWhile((element) => element is String)
      .cast();
}

void _getMessages(SendPort sp) async {
  await for (final now in Stream.periodic(
    const Duration(seconds: 1),
    (_) => DateTime.now().toIso8601String(),
  ).take(10)) {
    sp.send(now);
  }
  Isolate.exit(sp);
}

void testIt() async {
  await for (final msg in getMessages()) {
    msg.log();
  }
}

Network Image Retry in Flutter



@immutable
class RetryStrategy {
  final bool shouldRetry;
  final Duration waitBeforeRetry;
  const RetryStrategy({
    required this.shouldRetry,
    required this.waitBeforeRetry,
  });
}

typedef Retrier = RetryStrategy Function(String url, Object error);

class NetworkImageWithRetry extends StatelessWidget {
  final Widget loadingWidget;
  final Widget errorWidget;
  final String url;
  final Retrier retrier;

  final _controller = StreamController<Uint8List>.broadcast();

  NetworkImageWithRetry({
    Key? key,
    required this.url,
    required this.retrier,
    required this.loadingWidget,
    required this.errorWidget,
  }) : super(key: key);

  void getData() async {
    while (true == true) {
      try {
        final networkAsset = NetworkAssetBundle(Uri.parse(url));
        final loaded = await networkAsset.load(url);
        final bytes = loaded.buffer.asUint8List();
        _controller.sink.add(bytes);
        break;
      } catch (e) {
        final strategy = retrier(url, e);
        if (strategy.shouldRetry) {
          await Future.delayed(strategy.waitBeforeRetry);
        } else {
          _controller.sink.addError(e);
          break;
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    getData();
    return StreamBuilder(
      stream: _controller.stream,
      builder: (context, AsyncSnapshot<Uint8List> snapshot) {
        if (snapshot.hasError) {
          return errorWidget;
        } else {
          final data = snapshot.data;
          if (snapshot.hasData && data != null) {
            return Image.memory(data);
          } else {
            return loadingWidget;
          }
        }
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image Retry'),
      ),
      body: NetworkImageWithRetry(
        url: 'https://bit.ly/3qYOtDm',
        errorWidget: const Text('Got an error!'),
        loadingWidget: const Text('Loading...'),
        retrier: (url, error) {
          return RetryStrategy(
            shouldRetry: error is! FlutterError,
            waitBeforeRetry: const Duration(seconds: 1),
          );
        },
      ),
    );
  }
}

Reusable APIs in Flutter

import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:developer' as devtools show log;
import 'dart:convert' show utf8;
import 'package:meta/meta.dart' show useResult;

extension Log on Object {
  void log() => devtools.log(toString());
}

extension GetOnUri on Object {
  Future<HttpClientResponse> getUrl(
    String url,
  ) =>
      HttpClient()
          .getUrl(
            Uri.parse(
              url,
            ),
          )
          .then((req) => req.close());
}

mixin CanMakeGetCall {
  String get url;
  @useResult
  Future<String> getString() => getUrl(url).then(
        (response) => response
            .transform(
              utf8.decoder,
            )
            .join(),
      );
}

@immutable
class GetPeople with CanMakeGetCall {
  const GetPeople();
  @override
  String get url => 'http://127.0.0.1:5500/apis/people.json';
}

void testIt() async {
  final people = await const GetPeople().getString();
  devtools.log(people);
}

ListTile Shadow in Flutter



enum Currency { dollars }

extension Title on Currency {
  String get title {
    switch (this) {
      case Currency.dollars:
        return '\$';
    }
  }
}

@immutable
class Item {
  final IconData icon;
  final String name;
  final double price;
  final Currency currency;

  const Item({
    required this.icon,
    required this.name,
    required this.price,
    required this.currency,
  });

  String get description => '$price${currency.title}';
}

const items = [
  Item(
    icon: Icons.camera_alt,
    name: 'Camera',
    price: 300,
    currency: Currency.dollars,
  ),
  Item(
    icon: Icons.house,
    name: 'House',
    price: 1000000,
    currency: Currency.dollars,
  ),
  Item(
    icon: Icons.watch,
    name: 'Smart Watch',
    price: 200,
    currency: Currency.dollars,
  ),
];

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (_, index) {
          return ItemTile(
            item: items[index],
          );
        },
      ),
    );
  }
}

class ItemTile extends StatelessWidget {
  final Item item;
  const ItemTile({Key? key, required this.item}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Stack(
        children: [
          const TileBackground(),
          CustomTile(item: item),
        ],
      ),
    );
  }
}

class CustomTile extends StatelessWidget {
  final Item item;
  const CustomTile({
    Key? key,
    required this.item,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 7.0),
      child: Container(
        decoration: customDecoration(),
        child: ListTile(
          leading: Icon(
            item.icon,
            color: Colors.white,
          ),
          title: Text(item.name),
          subtitle: Text(item.description),
        ),
      ),
    );
  }
}

BoxDecoration customDecoration() {
  return BoxDecoration(
    color: const Color.fromARGB(255, 0x7d, 0xcf, 0xff),
    borderRadius: BorderRadius.circular(10.0),
    border: Border.all(
      color: Colors.black,
      width: 2.0,
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Positioned.fill(
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 6.0),
        child: Container(
          decoration: BoxDecoration(
            color: const Color.fromARGB(255, 202, 255, 127),
            borderRadius: BorderRadius.circular(10.0),
            border: Border.all(
              color: Colors.black,
              width: 2.0,
            ),
          ),
        ),
      ),
    );
  }
}

Transparent AppBar in Flutter



const images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        elevation: 0.0,
        backgroundColor: Colors.blueAccent.withAlpha(200),
        title: const Text('Transparent App Bar in Flutter'),
      ),
      body: const ImagesScrollView(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.only(top: 80.0),
      child: Padding(
        padding: const EdgeInsets.only(
          top: 40.0,
          left: 10.0,
          right: 10.0,
        ),
        child: Column(
          children: images
              .map((url) => ElevatedNetworkImage(url: url))
              .expand(
                (img) => [
                  img,
                  const SizedBox(height: 30.0),
                ],
              )
              .toList(),
        ),
      ),
    );
  }
}

class ElevatedNetworkImage extends StatelessWidget {
  final String url;
  const ElevatedNetworkImage({Key? key, required this.url}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PhysicalShape(
      color: Colors.white,
      clipper: Clipper(),
      elevation: 20.0,
      clipBehavior: Clip.none,
      shadowColor: Colors.white.withAlpha(200),
      child: CutEdges(
        child: Image.network(url),
      ),
    );
  }
}

class Clipper extends CustomClipper<Path> {
  static const variance = 0.2;
  static const reverse = 1.0 - variance;

  @override
  Path getClip(Size size) {
    final path = Path();

    path.moveTo(0.0, size.height * Clipper.variance);
    path.lineTo(size.width * Clipper.variance, 0.0);
    path.lineTo(size.width, 0.0);
    path.lineTo(size.width, size.height * Clipper.reverse);
    path.lineTo(size.width * Clipper.reverse, size.height);
    path.lineTo(0.0, size.height);
    path.lineTo(0.0, size.height * Clipper.variance);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

class CutEdges extends StatelessWidget {
  final Widget child;

  const CutEdges({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: Clipper(),
      child: child,
    );
  }
}

Constructors on Abstract Classes in Dart



import 'dart:developer' as devtools show log;

extension Log on Object {
  void log() => devtools.log(toString());
}

enum Type { dog, cat }

abstract class CanRun {
  final Type type;
  const CanRun({required this.type});
}

class Cat extends CanRun {
  const Cat() : super(type: Type.cat);
}

class Dog extends CanRun {
  const Dog() : super(type: Type.dog);
}

@useResult in Dart



import 'package:meta/meta.dart' show useResult;

class Person {
  final String firstName;
  final String lastName;

  const Person({
    required this.firstName,
    required this.lastName,
  });

  @useResult
  String fullName() => '$firstName $lastName';
}

void printFullName() {
  const Person(
    firstName: 'Foo',
    lastName: 'Bar',
  ).fullName();
}

@mustCallSuper in Dart



class Animal {
  @mustCallSuper
  void run() {}
}

class Dog extends Animal {
  @override
  void run() {}
}

Object.hash in Dart



class BreadCrumb {
  final bool isActive;
  final String name;
  BreadCrumb({
    required this.isActive,
    required this.name,
  });
  BreadCrumb activated() => BreadCrumb(
        isActive: true,
        name: name,
      );
  @override
  bool operator ==(covariant BreadCrumb other) =>
      isActive == other.isActive && name == other.name;

  @override
  int get hashCode => Object.hash(isActive, name);
}

Expanded Equally in Flutter



import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const HomePage(),
    ),
  );
}

extension ExpandEqually on Iterable<Widget> {
  Iterable<Widget> expandedEqually() => map(
        (w) => Expanded(
          flex: 1,
          child: w,
        ),
      );
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      body: Column(
        children: [
          Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              Container(
                height: 200,
                color: Colors.yellow,
              ),
              Container(
                height: 200,
                color: Colors.blue,
              ),
            ].expandedEqually().toList(),
          )
        ],
      ),
    );
  }
}

Random Iterable Value in Dart



import 'dart:math' as math show Random;

extension RandomElement<T> on Iterable<T> {
  T getRandomElement() => elementAt(
        math.Random().nextInt(length),
      );
}

final colors = [Colors.blue, Colors.red, Colors.brown];

class HomePage extends StatelessWidget {
  final color = ValueNotifier<MaterialColor>(
    colors.getRandomElement(),
  );
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('List.Random in Flutter'),
      ),
      body: ColorPickerButton(color: color),
    );
  }
}

class ColorPickerButton extends StatelessWidget {
  final ValueNotifier<MaterialColor> color;

  const ColorPickerButton({
    Key? key,
    required this.color,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<Color>(
      valueListenable: color,
      builder: (context, value, child) {
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: CenteredTight(
            child: TextButton(
              style: TextButton.styleFrom(backgroundColor: value),
              onPressed: () {
                color.value = colors.getRandomElement();
              },
              child: const Text(
                'Change color',
                style: TextStyle(
                  fontSize: 30,
                  color: Colors.white,
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

Hardcoded Strings in Flutter



extension Hardcoded on String {
  String get hardcoded => '$this 🧨';
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'My hardcoded string'.hardcoded,
        ),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('String in body'.hardcoded),
        ],
      ),
    );
  }
}

Manually Scroll in List View in Flutter

// Free Flutter Course 💙 https://linktr.ee/vandadnp

import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';

class HomePage extends StatelessWidget {
  final _controller = ItemScrollController();
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Testing'),
      ),
      body: ScrollablePositionedList.builder(
        itemScrollController: _controller,
        itemCount: allImages.length + 1,
        itemBuilder: (context, index) {
          if (index == 0) {
            return IndexSelector(
              count: allImages.length,
              onSelected: (index) {
                _controller.scrollTo(
                  index: index + 1,
                  duration: const Duration(milliseconds: 370),
                );
              },
            );
          } else {
            return ImageWithTitle(index: index);
          }
        },
      ),
    );
  }
}

class ImageWithTitle extends StatelessWidget {
  final int index;
  const ImageWithTitle({
    Key? key,
    required this.index,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          'Image $index',
          style: const TextStyle(fontSize: 30.0),
        ),
        Image.network(allImages.elementAt(index - 1)),
      ],
    );
  }
}

typedef OnIndexSelected = void Function(int index);

class IndexSelector extends StatelessWidget {
  final int count;
  final OnIndexSelected onSelected;
  final String prefix;
  const IndexSelector({
    Key? key,
    required this.count,
    required this.onSelected,
    this.prefix = 'Image',
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: Row(
        children: Iterable.generate(
          count,
          (index) => TextButton(
            onPressed: () {
              onSelected(index);
            },
            child: Text('$prefix ${index + 1}'),
          ),
        ).toList(),
      ),
    );
  }
}

const imageUrls = [
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

AsyncSnapshot to Widget in Flutter



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

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    ),
  );
}

final future = Future<String>.delayed(
  const Duration(seconds: 3),
  () => 'Hello world',
);

typedef ResolveToWidget<T> = Widget Function(
  ConnectionState connectionState,
  AsyncSnapshot<T> snapshot,
);

extension Materialize on AsyncSnapshot {
  Widget materialize(ResolveToWidget f) => f(
        connectionState,
        this,
      );
}

class HomePage extends HookWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hooks'),
      ),
      body: useFuture(future).materialize((connectionState, snapshot) {
        switch (connectionState) {
          case ConnectionState.done:
            return Text(snapshot.data ?? '');
          default:
            return const CircularProgressIndicator();
        }
      }),
    );
  }
}

Breadcrumbs in Flutter



@immutable
class BreadCrumbPath {
  final String title;
  final bool isActive;
  const BreadCrumbPath({
    required this.title,
    required this.isActive,
  });
  BreadCrumbPath activated() {
    return BreadCrumbPath(
      title: title,
      isActive: true,
    );
  }

  @override
  String toString() => title;
}

class BreatCrumbPathView extends StatelessWidget {
  final BreadCrumbPath path;

  const BreatCrumbPathView({
    Key? key,
    required this.path,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final title = path.isActive ? '${path.title} →' : path.title;
    return Padding(
      padding: const EdgeInsets.all(2.0),
      child: Text(
        title,
        style: TextStyle(
          height: 1.0,
          fontSize: 20.0,
          color: path.isActive ? Colors.blueAccent : Colors.black,
        ),
      ),
    );
  }
}

typedef OnBreadCrumbPathTapped = void Function(BreadCrumbPath path);

class BreadCrumbView extends StatelessWidget {
  final OnBreadCrumbPathTapped onTapped;
  final Stream<List<BreadCrumbPath>> paths;
  const BreadCrumbView({
    Key? key,
    required this.paths,
    required this.onTapped,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<BreadCrumbPath>>(
      stream: paths,
      builder: (context, snapshot) {
        final List<Widget> views;
        switch (snapshot.connectionState) {
          case ConnectionState.waiting:
          case ConnectionState.active:
            final paths = snapshot.data ?? [];
            final views = paths
                .map(
                  (path) => GestureDetector(
                    onTap: () => onTapped(path),
                    child: BreatCrumbPathView(path: path),
                  ),
                )
                .toList();
            return Wrap(
              spacing: 4.0,
              children: views,
              alignment: WrapAlignment.start,
              crossAxisAlignment: WrapCrossAlignment.center,
            );
          default:
            return Wrap();
        }
      },
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<BreadCrumbPath> _paths = [];
  late final TextEditingController _textController;
  late final StreamController<List<BreadCrumbPath>> _pathsController;

  @override
  void initState() {
    _pathsController = StreamController<List<BreadCrumbPath>>.broadcast(
      onListen: () {
        _pathsController.add(_paths);
      },
    );
    _textController = TextEditingController();
    super.initState();
  }

  @override
  void dispose() {
    _textController.dispose();
    _pathsController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Breadcrumb in Flutter'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            BreadCrumbView(
              paths: _pathsController.stream,
              onTapped: (path) async {
                await showBreadCrumbPathTappedDialog(
                  context,
                  path,
                );
              },
            ),
            TextField(
              controller: _textController,
              textAlign: TextAlign.center,
              decoration: const InputDecoration(
                hintText: 'Enter a new path here',
              ),
            ),
            TextButton(
              onPressed: () {
                _paths = [
                  ..._paths.map((p) => p.activated()),
                  BreadCrumbPath(
                    title: _textController.text,
                    isActive: false,
                  ),
                ];
                _pathsController.add(_paths);
                _textController.clear();
              },
              child: const Center(
                child: Text('Add new path'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Future<void> showBreadCrumbPathTappedDialog(
  BuildContext context,
  BreadCrumbPath path,
) {
  return showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        content: Text('You tapped on $path'),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: const Text('OK'),
          ),
        ],
      );
    },
  );
}

Unique Map Values in Dart



import 'dart:developer' as devtools show log;

extension ContainsDuplicateValues on Map {
  bool get containsDuplicateValues => 
    {...values}.length != values.length;
}

extension Log on Object {
  void log() => devtools.log(toString());
}

const people1 = {
  1: 'Foo',
  2: 'Bar',
};
const people2 = {
  1: 'Foo',
  2: 'Foo',
};

void testIt() {
  people1.containsDuplicateValues.log(); // false
  people2.containsDuplicateValues.log(); // true
}

Smart Quotes/Dashes in Flutter



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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Smart Quotes/Dashes in Flutter'),
      ),
      body: const Padding(
        padding: EdgeInsets.all(16.0),
        child: TextField(
          smartQuotesType: SmartQuotesType.disabled,
          smartDashesType: SmartDashesType.disabled,
          maxLines: null,
        ),
      ),
    );
  }
}

Haptic Feedback in Flutter



class CenteredTight extends StatelessWidget {
  final Widget child;
  const CenteredTight({
    Key? key,
    required this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [child],
    );
  }
}

class FullscreenImage extends StatefulWidget {
  final String imageUrl;
  const FullscreenImage({Key? key, required this.imageUrl}) : super(key: key);

  @override
  State<FullscreenImage> createState() => _FullscreenImageState();
}

class _FullscreenImageState extends State<FullscreenImage> {
  var shouldDisplayAppbar = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: shouldDisplayAppbar ? AppBar(title: const Text('Image')) : null,
      body: GestureDetector(
        onTap: () {
          setState(() => shouldDisplayAppbar = !shouldDisplayAppbar);
        },
        child: Image.network(
          widget.imageUrl,
          alignment: Alignment.center,
          width: double.infinity,
          height: double.infinity,
          fit: BoxFit.cover,
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Haptic Feedback in Flutter'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: CenteredTight(
          child: FractionallySizedBox(
            heightFactor: 0.7,
            child: GestureDetector(
              onLongPress: () async {
                await HapticFeedback.lightImpact();
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) {
                      return const FullscreenImage(
                        imageUrl: imageUrl,
                      );
                    },
                  ),
                );
              },
              child: Image.network(imageUrl),
            ),
          ),
        ),
      ),
    );
  }
}

Localization Delegates in Flutter



import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    MaterialApp(
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider<AuthBloc>(
        create: (context) => AuthBloc(FirebaseAuthProvider()),
        child: const HomePage(),
      ),
      routes: {
        createOrUpdateNoteRoute: (context) => const CreateUpdateNoteView(),
      },
    ),
  );
}

Extending Functions in Dart



import 'dart:developer' as devtools show log;

extension ToTextButton on VoidCallback {
  TextButton toTextButton(String title) {
    return TextButton(
      onPressed: this,
      child: Text(title),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Extensions in Flutter'),
      ),
      body: () {
        devtools.log('I am pressed');
      }.toTextButton('Press me'),
    );
  }
}

Paginated ListView in Flutter



@immutable
class Season {
  final String name;
  final String imageUrl;
  const Season({required this.name, required this.imageUrl});
  const Season.spring()
      : name = 'Spring',
        imageUrl = 'https://cnn.it/3xu58Ap';
  const Season.summer()
      : name = 'Summer',
        imageUrl = 'https://bit.ly/2VcCSow';
  const Season.autumn()
      : name = 'Autumn',
        imageUrl = 'https://bit.ly/3A3zStC';
  const Season.winter()
      : name = 'Winter',
        imageUrl = 'https://bit.ly/2TNY7wi';
}

const allSeasons = [
  Season.spring(),
  Season.summer(),
  Season.autumn(),
  Season.winter()
];

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    final height = width / (16.0 / 9.0);
    return Scaffold(
      appBar: AppBar(
        title: const Text('PageScrollPhysics in Flutter'),
      ),
      body: SizedBox(
        width: width,
        height: height,
        child: ListView(
          shrinkWrap: true,
          scrollDirection: Axis.horizontal,
          physics: const PageScrollPhysics(),
          clipBehavior: Clip.antiAlias,
          children: allSeasons.map((season) {
            return SizedBox(
              width: width,
              height: height,
              child: Image.network(
                season.imageUrl,
                height: height,
                fit: BoxFit.cover,
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

Immutable Classes in Dart



import 'package:flutter/foundation.dart' show immutable;

@immutable
abstract class Animal {
  final String name;
  const Animal(this.name);
}

class Cat extends Animal {
  const Cat() : super('Cindy Clawford');
}

class Dog extends Animal {
  int age;
  Dog()
      : age = 0,
        super('Bark Twain');
}

Card Widget in Flutter



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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Card in Flutter'),
      ),
      body: Image.network(
        'https://bit.ly/36fNNj9',
        frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
          return Card(
            child: child,
            clipBehavior: Clip.antiAlias,
          );
        },
        loadingBuilder: (context, child, loadingProgress) {
          final totalBytes = loadingProgress?.expectedTotalBytes;
          final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
          if (totalBytes != null && bytesLoaded != null) {
            return Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [CircularProgressIndicator()],
            );
          } else {
            return child;
          }
        },
      ),
    );
  }
}

List Equality Ignoring Ordering in Dart



@immutable
class Person {
  final String name;
  const Person(this.name);

  @override
  bool operator ==(covariant Person other) => other.name == name;

  @override
  int get hashCode => name.hashCode;

  @override
  String toString() => name;
}

const people1 = [Person('Foo'), Person('Bar'), Person('Baz')];
const people2 = [Person('Foo'), Person('Bar'), Person('Baz')];
const people3 = [Person('Bar'), Person('Bar'), Person('Baz')];
const people4 = [Person('Bar'), Person('Baz')];

extension IsEqualToIgnoringOrdering<T> on List<T> {
  bool isEqualToIgnoringOrdering(List<T> other) =>
      length == other.length &&
      {...this}.intersection({...other}).length == length;
}

void testIt() {
  assert(people1.isEqualToIgnoringOrdering(people2));
  assert(!people1.isEqualToIgnoringOrdering(people3));
  assert(!people2.isEqualToIgnoringOrdering(people3));
  assert(!people3.isEqualToIgnoringOrdering(people4));
}

Shorten GitHub URLs in Dart

// Want to support my work 🤝? https://buymeacoffee.com/vandad

import 'dart:developer' as devtools show log;
import 'dart:convert' show utf8;

Future<Uri> shortenGitHubUrl(String longUrl) =>
    HttpClient().postUrl(Uri.parse('https://git.io/')).then((req) {
      req.add(utf8.encode('url=$longUrl'));
      return req.close();
    }).then(
      (resp) async {
        try {
          final location = resp.headers[HttpHeaders.locationHeader]?.first;
          if (location != null) {
            return Uri.parse(location);
          } else {
            throw 'No location was specified';
          }
        } catch (e) {
          return Uri.parse(longUrl);
        }
      },
    );

void testIt() async {
  final uri = await shortenGitHubUrl(
      'https://github.com/vandadnp/flutter-tips-and-tricks');
  devtools.log(uri.toString());
  // logs https://git.io/JS5Fm
}

Time Picker in Flutter



class HomePage extends StatelessWidget {
  final timeOfDay = ValueNotifier<TimeOfDay?>(null);
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: timeOfDay,
      builder: (context, value, child) {
        final title = timeOfDay.value?.toString() ?? 'Time Picker in Flutter';
        return Scaffold(
          appBar: AppBar(title: Text(title)),
          body: Center(
            child: TextButton(
              onPressed: () async {
                timeOfDay.value = await showTimePicker(
                  context: context,
                  initialTime: TimeOfDay.now(),
                  initialEntryMode: TimePickerEntryMode.input,
                );
              },
              child: const Text('Please Pick a time'),
            ),
          ),
        );
      },
    );
  }
}

Throttled Print in Flutter



Stream<String> getStream() => Stream.periodic(
      const Duration(milliseconds: 100),
      (e) => DateTime.now().toString(),
    );

void testIt() async {
  await for (final now in getStream()) {
    debugPrintThrottled(now);
  }
}

Map Equality in Dart



typedef Name = String;
typedef Age = int;

const Map<Name, Age> people1 = {
  'foo': 20,
  'bar': 30,
  'baz': 40,
};

const Map<Name, Age> people2 = {
  'baz': 40,
  'foo': 20,
  'bar': 30,
};

void testIt() {
  assert(mapEquals(people1, people2));
}

Unique Maps in Dart



import 'dart:developer' as devtools show log;

typedef Name = String;
typedef Age = int;

const Map<Name, Age> people = {
  'foo': 20,
  'bar': 30,
  'baz': 20,
};

extension Unique<K, V> on Map<K, V> {
  Map<K, V> unique() {
    Map<K, V> result = {};
    for (final value in {...values}) {
      final firstKey = keys.firstWhereOrNull((key) => this[key] == value);
      if (firstKey != null) {
        result[firstKey] = value;
      }
    }
    return result;
  }
}

void testIt() {
  final uniques = people.unique();
  devtools.log(uniques.toString());
  // prints: {foo: 20, bar: 30}
}

Raw Auto Complete in Flutter



const emailProviders = [
  'gmail.com',
  'hotmail.com',
  'yahoo.com',
];

const icons = [
  'https://bit.ly/3HsvvvB',
  'https://bit.ly/3n6GW4L',
  'https://bit.ly/3zf2RLy',
];

class EmailTextField extends StatefulWidget {
  const EmailTextField({Key? key}) : super(key: key);

  @override
  State<EmailTextField> createState() => _EmailTextFieldState();
}

class _EmailTextFieldState extends State<EmailTextField> {
  late final TextEditingController _controller;
  late final FocusNode _focus;

  @override
  Widget build(BuildContext context) {
    return RawAutocomplete<String>(
      textEditingController: _controller,
      focusNode: _focus,
      fieldViewBuilder: (_, controller, focusNode, onSubmitted) {
        return TextFormField(
          controller: controller,
          focusNode: focusNode,
          onFieldSubmitted: (value) {
            onSubmitted();
          },
        );
      },
      optionsBuilder: (textEditingValue) {
        final lastChar = textEditingValue.text.characters.last;
        if (lastChar == '@') {
          return emailProviders;
        } else {
          return [];
        }
      },
      optionsViewBuilder: (context, onSelected, options) {
        return OptionsList(
          onSelected: onSelected,
          options: options,
          controller: _controller,
        );
      },
    );
  }

  @override
  void initState() {
    _controller = TextEditingController();
    _focus = FocusNode();
    super.initState();
  }

  @override
  void dispose() {
    _focus.dispose();
    _controller.dispose();
    super.dispose();
  }
}

class OptionsList extends StatelessWidget {
  final Iterable<String> options;
  final AutocompleteOnSelected<String> onSelected;
  final TextEditingController controller;
  const OptionsList({
    Key? key,
    required this.onSelected,
    required this.options,
    required this.controller,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.topLeft,
      child: Material(
        child: SizedBox(
          height: 150,
          child: ListView.builder(
            padding: const EdgeInsets.all(0.0),
            itemCount: options.length,
            itemBuilder: (context, index) {
              final option = options.elementAt(index);
              return GestureDetector(
                onTap: () => onSelected(controller.text + option),
                child: ListTile(
                  horizontalTitleGap: 2.0,
                  leading: Image.network(
                    icons[index],
                    width: 24,
                    height: 24,
                  ),
                  title: Text(option),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Raw Auto Complete in Flutter'),
      ),
      body: const Padding(
        padding: EdgeInsets.all(16.0),
        child: EmailTextField(),
      ),
    );
  }
}

Title on Object in Dart



import 'dart:developer' as devtools show log;

extension CapTitle on Object {
  String get capitalizedTitle {
    String str;
    if (this is Enum) {
      str = (this as Enum).name;
    } else {
      str = toString();
    }
    return str[0].toUpperCase() + str.substring(1);
  }
}

enum EmailProviders { gmail, yahoo, hotmail }

void testIt() {
  EmailProviders.values.map((p) => p.capitalizedTitle).forEach(devtools.log);
  // prints these:
  // Gmail
  // Yahoo
  // Hotmail
}

Compute in Flutter



import 'dart:developer' as devtools show log;
import 'dart:convert' show utf8, json;

@immutable
class Person {
  final String name;
  final int age;
  const Person(this.name, this.age);
  Person.fromJson(Map<String, dynamic> json)
      : name = json["name"] as String,
        age = json["age"] as int;
}

Future<Iterable<Person>> downloadAndParsePersons(Uri uri) => HttpClient()
    .getUrl(uri)
    .then((req) => req.close())
    .then((response) => response.transform(utf8.decoder).join())
    .then((jsonString) => json.decode(jsonString) as List<dynamic>)
    .then((json) => json.map((map) => Person.fromJson(map)));

void testIt() async {
  final persons = await compute(
    downloadAndParsePersons,
    Uri.parse('https://bit.ly/3Jjcw8R'),
  );
  devtools.log(persons.toString());
}

Filter on Map in Dart



import 'dart:developer' as devtools show log;

typedef Name = String;
typedef Age = int;

extension Filter<K, V> on Map<K, V> {
  Iterable<MapEntry<K, V>> filter(
    bool Function(MapEntry<K, V> entry) f,
  ) sync* {
    for (final entry in entries) {
      if (f(entry)) {
        yield entry;
      }
    }
  }
}

const Map<Name, Age> people = {
  'foo': 20,
  'bar': 31,
  'baz': 25,
  'qux': 32,
};

void testIt() async {
  final peopleOver30 = people.filter((e) => e.value > 30);
  devtools.log(peopleOver30.toString());
  // ☝🏻 prints (MapEntry(bar: 31), MapEntry(qux: 32))
}

Type Alias in Dart



const Map<String, int> people1 = {
  'foo': 20,
  'bar': 30,
  'baz': 25,
};

typedef Age = int;

const Map<String, Age> people2 = {
  'foo': 20,
  'bar': 30,
  'baz': 25,
};

ValueNotifier in Flutter



class DynamicToolTipTextField extends StatelessWidget {
  final TextInputType? keyboardType;
  final ValueNotifier<String?> hint;
  final TextEditingController controller;
  const DynamicToolTipTextField({
    Key? key,
    required this.hint,
    required this.controller,
    this.keyboardType,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: hint,
      builder: (context, value, child) {
        return TextField(
          keyboardType: keyboardType,
          controller: controller,
          decoration: InputDecoration(
            hintText: value as String?,
          ),
        );
      },
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

@immutable
abstract class HasText {
  String get text;
}

enum Hint { pleaseEnterYourEmail, youForgotToEnterYourEmail }

extension GetText on Hint {
  String get text {
    switch (this) {
      case Hint.pleaseEnterYourEmail:
        return 'Please enter your email';
      case Hint.youForgotToEnterYourEmail:
        return 'You forgot to enter your email';
    }
  }
}

class _HomePageState extends State<HomePage> {
  late final ValueNotifier<String?> _hint;
  late final TextEditingController _controller;

  @override
  void initState() {
    _hint = ValueNotifier<String?>(Hint.pleaseEnterYourEmail.text);
    _controller = TextEditingController();
    super.initState();
  }

  @override
  void dispose() {
    _hint.dispose();
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ValueNotifier in Flutter'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            DynamicToolTipTextField(
              hint: _hint,
              controller: _controller,
              keyboardType: TextInputType.emailAddress,
            ),
            TextButton(
              onPressed: () async {
                final email = _controller.text;
                if (email.trim().isEmpty) {
                  _hint.value = Hint.youForgotToEnterYourEmail.text;
                  await Future.delayed(const Duration(seconds: 2));
                  _hint.value = Hint.pleaseEnterYourEmail.text;
                }
              },
              child: const Text('Log in'),
            )
          ],
        ),
      ),
    );
  }
}

Object to Integer in Dart



enum ToIntStrategy { round, floor, ceil }

typedef ToIntOnErrorHandler = int Function(Object e);

extension ToInt on Object {
  int toInteger({
    ToIntStrategy strategy = ToIntStrategy.round,
    ToIntOnErrorHandler? onError,
  }) {
    try {
      final doubleValue = double.parse(toString());
      switch (strategy) {
        case ToIntStrategy.round:
          return doubleValue.round();
        case ToIntStrategy.floor:
          return doubleValue.floor();
        case ToIntStrategy.ceil:
          return doubleValue.ceil();
      }
    } catch (e) {
      if (onError != null) {
        return onError(e);
      } else {
        return -1;
      }
    }
  }
}

void testIt() {
  assert('xyz'.toInteger(onError: (_) => 100) == 100);
  assert(1.5.toInteger() == 2);
  assert(1.6.toInteger() == 2);
  assert('1.2'.toInteger(strategy: ToIntStrategy.floor) == 1);
  assert('1.2'.toInteger(strategy: ToIntStrategy.ceil) == 2);
  assert('1.5'.toInteger(strategy: ToIntStrategy.round) == 2);
}

Image Opacity in Flutter



class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _opacity;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    _opacity = Tween(begin: 0.0, end: 1.0).animate(_controller);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Image.network(
            'https://bit.ly/3ywI8l6',
            opacity: _opacity,
          ),
          Slider(
            value: _controller.value,
            onChanged: (value) {
              setState(() => _controller.value = value);
            },
          ),
        ],
      ),
    );
  }

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

Covariant in Dart

// Want to support my work 🤝? https://buymeacoffee.com/vandad

class Person {
  final String name;
  const Person(this.name);
  @override
  bool operator ==(Object other) {
    if (other is! Person) throw ArgumentError('Was expecting a person');
    return other.name == name;
  }

  @override
  int get hashCode => name.hashCode;
}

class Person {
  final String name;
  const Person(this.name);
  @override
  bool operator ==(covariant Person other) => other.name == name;

  @override
  int get hashCode => name.hashCode;
}

Custom Errors in Streams in Dart



class Either<V, E extends Exception> {
  final V? value;
  final E? error;

  const Either({this.value, this.error}) : assert((value ?? error) != null);

  bool get isError => error != null;
  bool get isValue => value != null;

  @override
  String toString() {
    if (value != null) {
      return "Value: $value";
    } else if (error != null) {
      return "Error: $error";
    } else {
      return 'Unknown state';
    }
  }
}

class DateTimeException implements Exception {
  final String reason;
  const DateTimeException({required this.reason});
}

Stream<Either<DateTime, DateTimeException>> getDateTime() async* {
  var index = 0;
  while (true) {
    if (index % 2 == 0) {
      yield Either(value: DateTime.now());
    } else {
      yield const Either(
        error: DateTimeException(reason: 'Something is wrong!'),
      );
    }
    index += 1;
  }
}

void testIt() async {
  await for (final value in getDateTime()) {
    dev.log(value.toString());
  }
}

Shake Animation in Flutter



class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

const animationWidth = 10.0;

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late final TextEditingController _textController;
  late final AnimationController _animationController;
  late final Animation<double> _offsetAnim;
  final defaultHintText = 'Please enter your email here 😊';
  var _hintText = '';

  @override
  void initState() {
    _hintText = defaultHintText;
    _textController = TextEditingController();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 370),
      vsync: this,
    );
    _offsetAnim = Tween(
      begin: 0.0,
      end: animationWidth,
    ).chain(CurveTween(curve: Curves.elasticIn)).animate(
          _animationController,
        )..addStatusListener(
        (status) {
          if (status == AnimationStatus.completed) {
            _animationController.reverse();
          }
        },
      );
    super.initState();
  }

  @override
  void dispose() {
    _textController.dispose();
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Shake Animation in Flutter'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            AnimatedBuilder(
              animation: _offsetAnim,
              builder: (context, child) {
                return Container(
                  margin: const EdgeInsets.symmetric(
                    horizontal: animationWidth,
                  ),
                  padding: EdgeInsets.only(
                    left: _offsetAnim.value + animationWidth,
                    right: animationWidth - _offsetAnim.value,
                  ),
                  child: TextField(
                    controller: _textController,
                    keyboardType: TextInputType.emailAddress,
                    decoration: InputDecoration(
                      hintText: _hintText,
                    ),
                  ),
                );
              },
            ),
            TextButton(
                onPressed: () async {
                  if (_textController.text.isEmpty) {
                    setState(() {
                      _hintText = 'You forgot to enter your email 🥲';
                      _animationController.forward(from: 0.0);
                    });
                    await Future.delayed(const Duration(seconds: 3));
                    setState(() {
                      _hintText = defaultHintText;
                    });
                  }
                },
                child: const Text('Login'))
          ],
        ),
      ),
    );
  }
}

Throw Enums in Dart



import 'dart:developer' as dev show log;

enum Exceptions { invalidUserName, invalidPassword }

void thisMethodThrows() {
  throw Exceptions.invalidPassword;
}

void testIt() {
  try {
    thisMethodThrows();
  } on Exceptions catch (e) {
    switch (e) {
      case (Exceptions.invalidUserName):
        dev.log("Invalid user name");
        break;
      case (Exceptions.invalidPassword):
        dev.log("Invalid password");
        break;
    }
  }
}

Future Error Test in Flutter



import 'dart:developer' as dev show log;

@immutable
abstract class UserException implements Exception {}

class InvalidUserNameException extends UserException {}

class InvalidUserAgeException extends UserException {}

@immutable
class User {
  final String name;
  final int age;
  User({required this.name, required this.age}) {
    if (!name.contains(RegExp(r'^[a-z ]+$'))) {
      throw InvalidUserNameException();
    } else if (age < 0 || age > 130) {
      throw InvalidUserAgeException();
    }
  }
  const User.anonymous()
      : name = 'Anonymous User',
        age = 0;
}

Future<User> getAsyncUser() => Future.delayed(
      const Duration(seconds: 1),
      () => User(name: 'Foo', age: 20),
    );

void testIt() async {
  final user = await getAsyncUser()
      .catchError(
        handleInvalidUsernameException,
        test: (e) => e is InvalidUserNameException,
      )
      .catchError(
        handleInvalidAgeException,
        test: (e) => e is InvalidUserAgeException,
      );
  dev.log(user.toString());
}

User handleInvalidUsernameException(Object? e) {
  dev.log(e.toString());
  return const User.anonymous();
}

User handleInvalidAgeException(Object? e) {
  dev.log(e.toString());
  return const User.anonymous();
}

Generic URL Retrieval in Dart



import 'dart:developer' as dev show log;

typedef StatusCodeResultBuilder<T> = Future<T> Function(
  int statusCode,
  HttpClientResponse response,
);

extension Get on Uri {
  Future<T?> getBody<T>({
    StatusCodeResultBuilder<T>? statusBuilder,
    T Function(Object error)? onNetworkError,
  }) async {
    try {
      final apiCall = await HttpClient().getUrl(this);
      final response = await apiCall.close();
      final builder = statusBuilder;
      if (builder == null) {
        final data = await response.transform(convert.utf8.decoder).join();
        if (data is T) {
          return data as T?;
        } else {
          return null;
        }
      } else {
        final result = await builder(response.statusCode, response);
        return result;
      }
    } catch (e) {
      if (onNetworkError != null) {
        return onNetworkError(e);
      } else {
        return null;
      }
    }
  }
}

extension ToUri on String {
  Uri toUri() => Uri.parse(this);
}

const url = 'https://bit.ly/3EKWcLa';

void testIt() async {
  final json = await url.toUri().getBody<String>(
    statusBuilder: (statusCode, response) async {
      if (statusCode == 200) {
        return await response.transform(convert.utf8.decoder).join();
      } else {
        return "{'error': 'Unexpected status code $statusCode'}";
      }
    },
    onNetworkError: (error) {
      return "{'error': 'Got network error'}";
    },
  );
  if (json != null) {
    dev.log(json);
  }
}

Custom Error Widget in Flutter



class MyErrorWidget extends StatelessWidget {
  final String text;
  const MyErrorWidget({Key? key, required this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Image.network('https://bit.ly/3gHlTCU'),
              Text(
                text,
                textAlign: TextAlign.center,
                style: const TextStyle(
                  color: Colors.red,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

void main() {
  ErrorWidget.builder = (FlutterErrorDetails details) {
    bool isInDebugMode = false;
    assert(() {
      isInDebugMode = true;
      return true;
    }());
    final message = details.exception.toString();
    if (isInDebugMode) {
      return MyErrorWidget(text: message);
    } else {
      return Text(
        message,
        textAlign: TextAlign.center,
      );
    }
  };

  runApp(
    const MaterialApp(
      home: HomePage(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Error Widget in Flutter'),
      ),
      body: Builder(
        builder: (context) {
          throw Exception(
              'Here is an exception that is caught by our custom Error Widget in Flutter');
        },
      ),
    );
  }
}

Handle Multiple Future Errors in Dart



import 'dart:developer' as dev show log;

Future<Iterable<T>> waitOn<T>(
  Iterable<Future<T>> futures,
  Function onError,
) async {
  List<T> result = [];
  for (final future in futures) {
    final value = await future.catchError(onError);
    result.add(value);
  }
  return result;
}

void testIt() async {
  final f1 = Future.error('First Error');
  final f2 = Future.delayed(const Duration(seconds: 2), () => 10);
  final f3 = Future.error('Second error');
  final f4 = Future.delayed(const Duration(seconds: 2), () => 'Hello world');
  final result = await waitOn([f1, f2, f3, f4], (error) => -1);
  dev.log(result.toString()); // [-1, 10, -1, Hello world]
}

Future Error Handling in Dart

import 'dart:developer' as dev show log;

extension OnError<T> on Future<T> {
  Future<T> onErrorJustReturn(T value) => catchError((_) => value);
}

Future<bool> isUserRegistered({required String email}) => HttpClient()
    .postUrl(Uri.parse('https://website'))
    .then((req) {
      req.headers.add('email', email);
      return req.close();
    })
    .then((resp) => resp.statusCode == 200)
    .onErrorJustReturn(false);

void testIt() async {
  final isFooRegistered = await isUserRegistered(email: 'foo@flutter.com');
  dev.log(isFooRegistered.toString());
}

String to Toast in Flutter



extension Toast on String {
  Future<void> showAsToast(BuildContext context,
      {required Duration duration}) async {
    final scaffold = ScaffoldMessenger.of(context);
    final controller = scaffold.showSnackBar(
      SnackBar(
        content: Text(this),
        backgroundColor: const Color(0xFF24283b),
        behavior: SnackBarBehavior.floating,
        elevation: 2.0,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
      ),
    );
    await Future.delayed(duration);
    controller.close();
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: TextButton(
          onPressed: () => 'Hello, World!'.showAsToast(
            context,
            duration: const Duration(seconds: 2),
          ),
          child: const Text('Show the snackbar'),
        ),
      ),
    );
  }
}

Waiting in Dart



Future<void> wait(Duration d) async {
  await Future.delayed(d);
}

extension Wait on int {
  Future<void> get seconds => wait(Duration(seconds: this));
  Future<void> get minutes => wait(Duration(minutes: this));
}

void testIt() async {
  await 2.seconds;
  'After 2 seconds'.log();
  await 3.minutes;
  'After 3 minutes'.log();
}

extension Log on Object {
  void log() {
    dev.log(toString());
  }
}

Loading Dialog in Flutter



typedef CloseDialog = void Function();

CloseDialog showLoadingScreen({
  required BuildContext context,
  required String text,
}) {
  final dialog = AlertDialog(
    content: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const CircularProgressIndicator(),
        const SizedBox(height: 10),
        Text(text),
      ],
    ),
  );
  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (_) => dialog,
  );

  return () => Navigator.of(context).pop();
}

void testIt(BuildContext context) async {
  final closeDialog = showLoadingScreen(
    context: context,
    text: 'Loading data...',
  );
  await Future.delayed(const Duration(seconds: 2));
  closeDialog();
}

Compact Map on Map<K,V> in Dart



const foo = 'foo';
const bar = 'bar';
const baz = 'baz';

const namesAndAges = {
  foo: 20,
  bar: 25,
  baz: 18,
};

const acceptedNames = [
  foo,
  bar,
];

void testIt() {
  final acceptedAges = namesAndAges.compactMap(
    (e) => acceptedNames.contains(e.key) ? e.value : null,
  );
  acceptedAges.log(); // [20, 25]
}

extension CompactMap<T, E> on Map<T, E> {
  Iterable<V> compactMap<V>(V? Function(MapEntry<T, E>) f) sync* {
    for (final entry in entries) {
      final extracted = f(entry);
      if (extracted != null) {
        yield extracted;
      }
    }
  }
}

Query Parameters in Dart



import 'dart:developer' as devtools show log;

const host = 'freecurrencyapi.net';
const path = '/api/v2/latest';
const apiKey = 'YOUR_API_KEY';
const baseCurrency = 'sek';
const params = {
  'apiKey': apiKey,
  'base_currency': 'sek',
};

void insteadOfThis() {
  const url = 'https://$host$path?apiKey=$apiKey&base_currency=$baseCurrency';
  url.log();
}

void doThis() {
  final url = Uri.https(host, path, params);
  url.log();
}

extension Log on Object {
  void log() {
    devtools.log(toString());
  }
}

Multiple Gradients in Container in Flutter



typedef GradientContainersBuilder = Map<LinearGradient, Widget?> Function();

class GradientContainers extends StatelessWidget {
  final GradientContainersBuilder builder;

  const GradientContainers({
    Key? key,
    required this.builder,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: builder().entries.map((mapEntry) {
        final gradient = mapEntry.key;
        final widget = mapEntry.value;
        return GradientContainer(
          gradient: gradient,
          child: widget,
        );
      }).toList(),
    );
  }
}

class GradientContainer extends StatelessWidget {
  final LinearGradient gradient;
  final Widget? child;

  const GradientContainer({Key? key, required this.gradient, this.child})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Positioned.fill(
      child: Container(
        decoration: BoxDecoration(
          gradient: gradient,
        ),
        child: child,
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GradientContainers(
        builder: () => {
          topLeftToBottomRightGradient: null,
          rightToLeftGradient: null,
          leftToRightGradinet: null,
          bottomRightGradient: Image.network('https://bit.ly/3otHHog'),
        },
      ),
    );
  }
}

const transparent = Color(0x00FFFFFF);

const topLeftToBottomRightGradient = LinearGradient(
  begin: Alignment.topLeft,
  end: Alignment.bottomRight,
  colors: [
    Color(0xff2ac3de),
    transparent,
  ],
);

const bottomRightGradient = LinearGradient(
  begin: Alignment.bottomRight,
  end: Alignment.topLeft,
  colors: [
    Color(0xffbb9af7),
    transparent,
  ],
);

const rightToLeftGradient = LinearGradient(
  begin: Alignment.centerRight,
  end: Alignment.centerLeft,
  colors: [
    Color(0xff9ece6a),
    transparent,
  ],
);

const leftToRightGradinet = LinearGradient(
  begin: Alignment.centerLeft,
  end: Alignment.centerRight,
  colors: [
    Color(0xff7dcfff),
    transparent,
  ],
);

void main() {
  runApp(
    const MaterialApp(
      home: HomePage(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

Filter on Stream<List<T>> in Dart



import 'dart:developer' as devtools show log;

extension Filter<T> on Stream<List<T>> {
  Stream<List<T>> filter(bool Function(T) where) =>
      map((items) => items.where(where).toList());
}

final Stream<List<int>> allNumbers = Stream.periodic(
  const Duration(seconds: 1),
  (value) => [for (var i = 0; i < value; i++) i],
);

bool isEven(num value) => value % 2 == 0;
bool isOdd(num value) => value % 2 != 0;

extension EvenOdd<E extends num> on Stream<List<E>> {
  Stream<List<E>> get evenNumbers => filter(isEven);
  Stream<List<E>> get oddNumbers => filter(isOdd);
}

void readEvenNumbers() async {
  await for (final evenNumber in allNumbers.evenNumbers) {
    devtools.log('All even numbers: $evenNumber');
  }
}

void readOddNumbers() async {
  await for (final oddNumber in allNumbers.oddNumbers) {
    devtools.log('All odd numbers: $oddNumber');
  }
}

Generic Route Arguments in Flutter

extension GetArgument on BuildContext {
  T? getArgument<T>() {
    final modalRoute = ModalRoute.of(this);
    if (modalRoute != null) {
      final args = modalRoute.settings.arguments;
      if (args != null && args is T) {
        return args as T;
      }
    }
    return null;
  }
}

Generic Dialog in Flutter

typedef DialogOptionBuilder<T> = Map<String, T> Function();

Future<T?> showGenericDialog<T>({
  required BuildContext context,
  required String title,
  required String content,
  required DialogOptionBuilder optionsBuilder,
}) {
  final options = optionsBuilder();
  return showDialog<T>(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text(title),
        content: Text(content),
        actions: options.keys.map(
          (optionTitle) {
            final T value = options[optionTitle];
            return TextButton(
              onPressed: () {
                Navigator.of(context).pop(value);
              },
              child: Text(optionTitle),
            );
          },
        ).toList(),
      );
    },
  );
}

Future<bool> showLogOutDialog(BuildContext context) {
  return showGenericDialog<bool>(
    context: context,
    title: 'Log out',
    content: 'Are you sure you want to log out?',
    optionsBuilder: () => {
      'Cancel': false,
      'Log out': true,
    },
  ).then(
    (value) => value ?? false,
  );
}

GitHub API in Flutter



import 'dart:io' show HttpHeaders, HttpClient;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:convert' show utf8, json;

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

Future<Iterable<GithubUser>> getGithubFollowers(String accessToken) =>
    HttpClient()
        .getUrl(Uri.parse('https://api.github.com/user/followers'))
        .then((req) {
          req.headers
            ..set(HttpHeaders.authorizationHeader, 'Bearer $accessToken')
            ..set(HttpHeaders.contentTypeHeader, 'application/json');
          return req.close();
        })
        .then((resp) => resp.transform(utf8.decoder).join())
        .then((jsonStr) => json.decode(jsonStr) as List<dynamic>)
        .then(
          (jsonArray) => jsonArray.compactMap((element) {
            if (element is Map<String, dynamic>) {
              return element;
            } else {
              return null;
            }
          }),
        )
        .then(
          (listOfMaps) => listOfMaps.map(
            (map) => GithubUser.fromJson(map),
          ),
        );

class GithubUser {
  final String username;
  final String avatarUrl;

  GithubUser.fromJson(Map<String, dynamic> json)
      : username = json['login'] as String,
        avatarUrl = json['avatar_url'] as String;
}

extension CompactMap<T> on List<T> {
  List<E> compactMap<E>(E? Function(T element) f) {
    Iterable<E> imp(E? Function(T element) f) sync* {
      for (final value in this) {
        final mapped = f(value);
        if (mapped != null) {
          yield mapped;
        }
      }
    }

    return imp(f).toList();
  }
}

const token = 'PUT_YOUR_TOKEN_HERE';

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GitHub API in Flutter'),
      ),
      body: FutureBuilder(
        future: getGithubFollowers(token),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.done:
              final users = (snapshot.data as Iterable<GithubUser>).toList();
              return ListView.builder(
                itemCount: users.length,
                itemBuilder: (context, index) {
                  final user = users[index];
                  return ListTile(
                    title: Text(user.username),
                    leading: CircularAvatar(url: user.avatarUrl),
                  );
                },
              );
            default:
              return const CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: HomePage(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

ChangeNotifier in Flutter



import 'package:provider/provider.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

const allImages = [
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class ImageData {
  final Uint8List imageData;
  const ImageData(this.imageData);
}

class Images extends ChangeNotifier {
  final List<ImageData> _items = [];

  var _isLoading = false;
  bool get isLoading => _isLoading;

  UnmodifiableListView<ImageData> get items => UnmodifiableListView(_items);

  void loadNextImage() async {
    if (_items.length < allImages.length) {
      // time to load more
      _isLoading = true;
      notifyListeners();
      final imageUrl = allImages[_items.length];
      final networkAsset = NetworkAssetBundle(Uri.parse(imageUrl));
      final loaded = await networkAsset.load(imageUrl);
      final bytes = loaded.buffer.asUint8List();
      final imageData = ImageData(bytes);
      _items.insert(0, imageData);
      _isLoading = false;
      notifyListeners();
    } else {
      if (isLoading) {
        _isLoading = false;
        notifyListeners();
      }
    }
  }
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ChangeNotifier in Flutter'),
        actions: [
          Consumer<Images>(
            builder: (context, value, child) {
              return IconButton(
                onPressed: () {
                  value.loadNextImage();
                },
                icon: const Icon(Icons.add_box_outlined),
              );
            },
          )
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Consumer<Images>(
          builder: (context, value, child) {
            final images = value.items;
            final isLoading = value.isLoading;
            return ListView.builder(
              itemBuilder: (context, index) {
                if (index == 0 && isLoading) {
                  return Center(
                    child: Column(
                      children: const [
                        CircularProgressIndicator(),
                        SizedBox(height: 16.0),
                      ],
                    ),
                  );
                } else {
                  final imageIndex = isLoading ? index - 1 : index;
                  final imageData = images[imageIndex].imageData;
                  return Column(
                    children: [
                      RoundedImageWithShadow(imageData: imageData),
                      const SizedBox(height: 16.0),
                    ],
                  );
                }
              },
              itemCount: isLoading ? images.length + 1 : images.length,
            );
          },
        ),
      ),
    );
  }
}

class RoundedImageWithShadow extends StatelessWidget {
  final Uint8List imageData;
  const RoundedImageWithShadow({Key? key, required this.imageData})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      clipBehavior: Clip.antiAlias,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            blurRadius: 2,
            color: Colors.black.withAlpha(40),
            spreadRadius: 2,
          ),
        ],
      ),
      child: Image.memory(
        imageData,
        fit: BoxFit.cover,
      ),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      home: ChangeNotifierProvider(
        create: (_) => Images(),
        child: const HomePage(),
      ),
      debugShowCheckedModeBanner: false,
    ),
  );
}

Refresh Indicator in Flutter



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

const allImages = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _images = [allImages.first];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Refresh Indicator in Flutter'),
      ),
      body: RefreshIndicator(
        onRefresh: () async {
          final nextIndex = _images.length + 1;
          if (nextIndex < allImages.length) {
            setState(() {
              _images.insert(0, allImages[nextIndex]);
            });
          }
        },
        child: ListView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
          padding: const EdgeInsets.all(16),
          itemCount: _images.length,
          itemBuilder: (context, index) {
            final imageUrl = _images[index];
            return Column(
              children: [
                RoundedImageWithShadow(url: imageUrl),
                const SizedBox(height: 16),
              ],
            );
          },
        ),
      ),
    );
  }
}

class RoundedImageWithShadow extends StatelessWidget {
  final String url;
  const RoundedImageWithShadow({Key? key, required this.url}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      clipBehavior: Clip.antiAlias,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            blurRadius: 2,
            color: Colors.black.withAlpha(40),
            spreadRadius: 2,
          ),
        ],
      ),
      child: Image.network(url),
    );
  }
}

FlatMap in Dart



extension FlatMap<T> on T? {
  E? flatMap<E>(E? Function(T) f) => this != null ? f(this!) : null;
}

AuthUser? get insteadOfThis {
  final user = FirebaseAuth.instance.currentUser;
  if (user != null) {
    return AuthUser.fromFirebase(user);
  } else {
    return null;
  }
}

AuthUser? get doThis =>
    FirebaseAuth.instance.currentUser.flatMap((u) => AuthUser.fromFirebase(u));

OrientationBuilder in Flutter



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

class RoundedImageWithShadow extends StatelessWidget {
  final String url;
  const RoundedImageWithShadow({Key? key, required this.url}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      clipBehavior: Clip.antiAlias,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            blurRadius: 2,
            color: Colors.black.withAlpha(40),
            spreadRadius: 2,
          ),
        ],
      ),
      child: Image.network(url),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: OrientationBuilder(
          builder: (context, orientation) {
            final int count;
            switch (orientation) {
              case Orientation.portrait:
                count = 2;
                break;
              case Orientation.landscape:
                count = 4;
                break;
            }
            return GridView.count(
              padding: const EdgeInsets.all(8.0),
              crossAxisCount: count,
              mainAxisSpacing: 8.0,
              crossAxisSpacing: 8.0,
              children: images
                  .map((url) => RoundedImageWithShadow(url: url))
                  .toList(),
            );
          },
        ),
      ),
    );
  }
}

final images = [
  'https://bit.ly/3qJ2FCf',
  'https://bit.ly/3Hs9JsV',
  'https://bit.ly/3cfT6Cv',
  'https://bit.ly/30wGnIE',
  'https://bit.ly/3kJYsum',
  'https://bit.ly/3oDoMaJ',
  'https://bit.ly/3FndXQM',
  'https://bit.ly/3ci4i1f',
];

Linear Gradient in Flutter



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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Linear Gradient in Flutter'),
      ),
      body: const ImageWithShadow(url: 'https://bit.ly/3otHHog'),
    );
  }
}

class ImageWithShadow extends StatelessWidget {
  final String url;

  const ImageWithShadow({
    Key? key,
    required this.url,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Stack(
        children: [
          Positioned.fill(
            child: Container(
              decoration: BoxDecoration(
                boxShadow: [
                  BoxShadow(
                    blurRadius: 10.0,
                    color: Colors.black.withOpacity(0.5),
                    offset: const Offset(0.0, 3.0),
                  )
                ],
                borderRadius: const BorderRadius.all(Radius.circular(20)),
                gradient: const LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: [
                    Color.fromARGB(255, 176, 229, 251),
                    Color.fromARGB(255, 235, 202, 250)
                  ],
                ),
              ),
            ),
          ),
          Image.network(url),
        ],
      ),
    );
  }
}

Bloc Text Editing Controller in Flutter

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

abstract class Event {
  const Event();
}

class SearchEvent extends Event {
  final String searchString;
  const SearchEvent(this.searchString);
}

class ClearSearch extends Event {}

class SearchBloc extends Bloc<Event, List<String>> {
  static const names = ['foo', 'bar', 'baz'];

  SearchBloc() : super(names) {
    on<Event>((event, emit) {
      if (event is SearchEvent) {
        emit(names
            .where((element) => element.contains(event.searchString))
            .toList());
      } else if (event is ClearSearch) {
        emit(names);
      }
    });
  }
}

class BlocTextEditingController extends TextEditingController {
  SearchBloc? bloc;
  BlocTextEditingController() {
    addListener(() {
      if (text.isEmpty) {
        bloc?.add(ClearSearch());
      } else {
        bloc?.add(SearchEvent(text));
      }
    });
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

const largeStyle = TextStyle(fontSize: 30);

class _HomePageState extends State<HomePage> {
  late final BlocTextEditingController _controller;

  @override
  void initState() {
    _controller = BlocTextEditingController();
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    _controller.bloc = BlocProvider.of<SearchBloc>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc Search in Flutter'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: BlocBuilder<SearchBloc, List<String>>(
          builder: (context, state) {
            return ListView.builder(
              itemBuilder: (context, index) {
                if (index == 0) {
                  // search field
                  return TextField(
                    decoration: InputDecoration(
                      hintText: 'Enter search term here...',
                      hintStyle: largeStyle,
                    ),
                    style: largeStyle,
                    controller: _controller,
                  );
                } else {
                  final name = state[index - 1];
                  return ListTile(
                    title: Text(
                      name,
                      style: largeStyle,
                    ),
                  );
                }
              },
              itemCount: state.length + 1, // +1 for search
            );
          },
        ),
      ),
    );
  }
}

Blurred TabBar in Flutter



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

const images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

class CustomTabBar extends StatelessWidget {
  final List<IconButton> buttons;

  const CustomTabBar({Key? key, required this.buttons}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomCenter,
      child: ClipRect(
        child: Container(
          height: 80,
          color: Colors.white.withOpacity(0.4),
          child: BackdropFilter(
            filter: ImageFilter.blur(sigmaX: 4.0, sigmaY: 4.0),
            child: Container(
              width: double.infinity,
              decoration: BoxDecoration(
                color: Colors.white.withOpacity(0.5),
              ),
              child: Padding(
                padding: const EdgeInsets.only(bottom: 15),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: buttons,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

const summerIcon = Icon(
  Icons.surfing,
  size: 40.0,
  color: Colors.teal,
);

const autumnIcon = Icon(
  Icons.nature_outlined,
  size: 40.0,
  color: Colors.black45,
);

const winterIcon = Icon(
  Icons.snowboarding,
  size: 40.0,
  color: Colors.black45,
);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Blurred Tab Bar'),
      ),
      body: Stack(
        children: [
          ListView.builder(
            itemCount: images.length,
            itemBuilder: (context, index) {
              final url = images[index];
              return Image.network(url);
            },
          ),
          CustomTabBar(
            buttons: [
              IconButton(
                icon: summerIcon,
                onPressed: () {
                  // implement me
                },
              ),
              IconButton(
                icon: autumnIcon,
                onPressed: () {
                  // implement me
                },
              ),
              IconButton(
                icon: winterIcon,
                onPressed: () {
                  // implement me
                },
              )
            ],
          )
        ],
      ),
    );
  }
}

Play YouTube in Flutter



import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

const videoIds = [
  'BHACKCNDMW8',
  '26h9hBZFl7w',
  'glENND73k4Q',
  'd0tU18Ybcvk',
];

class VideoView extends StatelessWidget {
  final String videoId;
  final _key = UniqueKey();

  VideoView({required this.videoId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Watch a Video'),
      ),
      body: Center(
        child: Container(
          height: 232.0,
          child: WebView(
            key: _key,
            initialUrl: 'https://www.youtube.com/embed/$videoId',
            javascriptMode: JavascriptMode.unrestricted,
          ),
        ),
      ),
    );
  }
}

class YouTubeVideoThumbnail extends StatelessWidget {
  final String videoId;
  final String thumbnailUrl;

  const YouTubeVideoThumbnail({Key? key, required this.videoId})
      : thumbnailUrl = 'https://img.youtube.com/vi/$videoId/maxresdefault.jpg',
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (_) => VideoView(videoId: videoId),
          ),
        );
      },
      child: Container(
        height: 256.0,
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          boxShadow: [
            BoxShadow(
              blurRadius: 10.0,
              color: Colors.black.withAlpha(50),
              spreadRadius: 10.0,
            ),
          ],
          borderRadius: BorderRadius.circular(20),
          image: DecorationImage(
            fit: BoxFit.fitHeight,
            image: NetworkImage(thumbnailUrl),
          ),
        ),
        child: Center(
          child: Icon(
            Icons.play_arrow,
            color: Colors.white,
            size: 100.0,
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('YouTube Videos in Flutter')),
      body: ListView.builder(
        itemCount: videoIds.length,
        itemBuilder: (context, index) {
          final videoId = videoIds[index];
          return Padding(
            padding: const EdgeInsets.all(8.0),
            child: YouTubeVideoThumbnail(videoId: videoId),
          );
        },
      ),
    );
  }
}

ListView Background in Flutter



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

class ListItem {
  const ListItem();
  factory ListItem.emptyTile() => EmptyTile();
  factory ListItem.tile(
    String title,
    String subTitle,
  ) =>
      Tile(
        title,
        subTitle,
      );
}

class Tile extends ListItem {
  final String title;
  final String subTitle;
  const Tile(this.title, this.subTitle) : super();
}

class EmptyTile extends ListItem {}

final items = [
  for (var i = 1; i <= 6; i++) ListItem.tile('Title $i', 'Sub title $i'),
  ListItem.emptyTile(),
  for (var i = 7; i <= 12; i++) ListItem.tile('Title $i', 'Sub title $i'),
];

class Background extends StatelessWidget {
  final Widget child;
  const Background({Key? key, required this.child}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      clipBehavior: Clip.antiAlias,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        image: DecorationImage(
          fit: BoxFit.fitHeight,
          image: NetworkImage('https://bit.ly/3jXSDto'),
        ),
      ),
      child: child,
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Background(
        child: ListView.builder(
          padding: EdgeInsets.zero,
          itemCount: items.length,
          itemBuilder: (context, index) {
            final item = items[index];
            if (item is Tile) {
              return Container(
                color: Colors.grey[200],
                child: ListTile(
                  title: Text(item.title),
                  subtitle: Text(item.subTitle),
                ),
              );
            } else if (item is EmptyTile) {
              return SizedBox(
                height: 450,
              );
            } else {
              throw 'unexpcted item';
            }
          },
        ),
      ),
    );
  }
}

Integer to Binary in Dart

extension ToBinary on int {
  String toBinary(
    int len, {
    int separateAtLength = 4,
    String separator = ',',
  }) =>
      toRadixString(2)
          .padLeft(len, '0')
          .splitByLength(separateAtLength)
          .join(separator);
}

void testIt() {
  assert(1.toBinary(8) == '0000,0001');
  assert(2.toBinary(4) == '0010');
  assert(3.toBinary(16) == '0000,0000,0000,0011');
  assert(255.toBinary(8, separateAtLength: 8) == '11111111');
  assert(255.toBinary(8, separateAtLength: 4) == '1111,1111');
}

extension SplitByLength on String {
  Iterable<String> splitByLength(int len, {String filler = '0'}) sync* {
    final missingFromLength =
        length % len == 0 ? 0 : len - (characters.length % len);
    final expectedLength = length + missingFromLength;
    final src = padLeft(expectedLength, filler);
    final chars = src.characters;
    for (var i = 0; i < chars.length; i += len) {
      yield chars.getRange(i, i + len).toString();
    }
  }
}

Split String by Length in Dart



void testIt() {
  assert('dartlang'
      .splitByLength(5, filler: '💙')
      .isEqualTo(['💙💙dar', 'tlang']));

  assert('0100010'.splitByLength(4).isEqualTo(['0010', '0010']));
  assert('foobar'.splitByLength(3).isEqualTo(['foo', 'bar']));
  assert('flutter'.splitByLength(4, filler: 'X').isEqualTo(['Xflu', 'tter']));
  assert('dart'.splitByLength(5, filler: '').isEqualTo(['dart']));
}

extension SplitByLength on String {
  Iterable<String> splitByLength(int len, {String filler = '0'}) sync* {
    final missingFromLength =
        length % len == 0 ? 0 : len - (characters.length % len);
    final expectedLength = length + missingFromLength;
    final src = padLeft(expectedLength, filler);
    final chars = src.characters;
    for (var i = 0; i < chars.length; i += len) {
      yield chars.getRange(i, i + len).toString();
    }
  }
}

Image Tint in Flutter



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

enum OverlayColor { brown, orange, yellow, green, blue }

extension Color on OverlayColor {
  MaterialColor get color {
    switch (this) {
      case OverlayColor.blue:
        return Colors.blue;
      case OverlayColor.brown:
        return Colors.brown;
      case OverlayColor.green:
        return Colors.green;
      case OverlayColor.orange:
        return Colors.orange;
      case OverlayColor.yellow:
        return Colors.yellow;
    }
  }
}

extension Title on OverlayColor {
  String get title => toString().split('.').last;
}

extension ToTextButtonWithValue on OverlayColor {
  TextButtonWithValue<OverlayColor> toTextButtonWithValue(
      OnTextButtonWithValuePressed onPressed) {
    return TextButtonWithValue(
      value: this,
      onPressed: onPressed,
      child: Text(title),
    );
  }
}

typedef OnTextButtonWithValuePressed<T> = void Function(T value);

class TextButtonWithValue<T> extends StatelessWidget {
  final T value;
  final OnTextButtonWithValuePressed onPressed;
  final Widget child;
  const TextButtonWithValue({
    Key? key,
    required this.value,
    required this.onPressed,
    required this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () {
        onPressed(value);
      },
      child: child,
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  OverlayColor? _overlayColor;

  ColorFilter? getcolorFilter() {
    final overlayColor = _overlayColor;
    if (overlayColor == null) {
      return null;
    }
    return ColorFilter.mode(
      overlayColor.color,
      BlendMode.colorBurn,
    );
  }

  Iterable<Widget> overlayColorButtons() {
    return OverlayColor.values.map((overlayColor) {
      return Expanded(
        flex: 1,
        child: Container(
          child: overlayColor.toTextButtonWithValue(
            (value) {
              setState(() {
                _overlayColor = value;
              });
            },
          ),
        ),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Tinting Images in Flutter'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Container(
              height: 250.0,
              clipBehavior: Clip.antiAlias,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(20),
                image: DecorationImage(
                  colorFilter: getcolorFilter(),
                  fit: BoxFit.fitHeight,
                  image: NetworkImage('https://bit.ly/3jOueGG'),
                ),
              ),
            ),
            SizedBox(height: 16.0),
            Row(
              children: overlayColorButtons().toList(),
            )
          ],
        ),
      ),
    );
  }
}

SlideTransition in Flutter



import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late final _controller = AnimationController(
    duration: Duration(milliseconds: 500),
    vsync: this,
  );

  late final _animation = Tween<Offset>(
    begin: Offset(0.0, 0.0),
    end: Offset(-0.83, 0.0),
  ).animate(
    CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInQuint,
    ),
  );

  var _isExpanded = false;

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

  @override
  Widget build(BuildContext context) {
    _controller.forward();
    return Scaffold(
      body: SizedBox.expand(
        child: Stack(
          fit: StackFit.passthrough,
          children: [
            Image.network(
              'https://bit.ly/3BWYDbz',
              fit: BoxFit.fitHeight,
            ),
            Positioned(
              top: 200.0,
              child: SlideTransition(
                position: _animation,
                child: GestureDetector(
                  onTap: () {
                    _isExpanded = !_isExpanded;
                    if (_isExpanded) {
                      _controller.reverse();
                    } else {
                      _controller.forward();
                    }
                  },
                  child: Box(),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.blue[200]?.withAlpha(200),
        border: Border.all(
          color: Colors.blue,
          style: BorderStyle.solid,
          width: 1.0,
        ),
        borderRadius: BorderRadius.only(
          topRight: Radius.circular(10),
          bottomRight: Radius.circular(10),
        ),
      ),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          children: [
            Text(
              'By: Jesper Anhede',
              style: TextStyle(
                fontSize: 18.0,
              ),
            ),
            SizedBox(width: 10.0),
            Icon(
              Icons.info,
              color: Colors.pink[400],
            ),
          ],
        ),
      ),
    );
  }
}

Expansion Panels and Lists in Flutter



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

class Event {
  final String title;
  final String details;
  final String imageUrl;
  bool isExpanded = false;

  Event({
    required this.title,
    required this.details,
    required this.imageUrl,
  });

  @override
  bool operator ==(covariant Event other) => title == other.title;
}

const diwaliDetails =
    '''Diwali, or Dipawali, is India's biggest and most important holiday of the year. The festival gets its name from the row (avali) of clay lamps (deepa) that Indians light outside their homes to symbolize the inner light that protects from spiritual darkness. This festival is as important to Hindus as the Christmas holiday is to Christians.''';

const halloweenDetails =
    '''Halloween or Hallowe'en, less commonly known as Allhalloween, All Hallows' Eve, or All Saints' Eve, is a celebration observed in many countries on 31 October, the eve of the Western Christian feast of All Hallows' Day.''';

final events = [
  Event(
    title: 'Diwali',
    details: diwaliDetails,
    imageUrl: 'https://bit.ly/3mGg8YW',
  ),
  Event(
    title: 'Halloween',
    details: halloweenDetails,
    imageUrl: 'https://bit.ly/3wb1w7j',
  ),
];

extension ToPanel on Event {
  ExpansionPanel toPanel() {
    return ExpansionPanel(
      headerBuilder: (context, isExpanded) {
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text(
            title,
            style: TextStyle(fontSize: 30.0),
          ),
        );
      },
      isExpanded: isExpanded,
      body: Container(
        height: 250,
        width: double.infinity,
        decoration: BoxDecoration(
          image: DecorationImage(
            fit: BoxFit.fitWidth,
            colorFilter: ColorFilter.mode(
              Colors.black.withOpacity(0.5),
              BlendMode.luminosity,
            ),
            image: NetworkImage(imageUrl),
          ),
        ),
        child: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(
              details,
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 20, color: Colors.white, shadows: [
                Shadow(
                  blurRadius: 1.0,
                  offset: Offset.zero,
                  color: Colors.black,
                )
              ]),
            ),
          ),
        ),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Expansion Panels in Flutter'),
      ),
      body: SingleChildScrollView(
        child: ExpansionPanelList(
          children: events.map((e) => e.toPanel()).toList(),
          expansionCallback: (panelIndex, isExpanded) {
            setState(() {
              events[panelIndex].isExpanded = !isExpanded;
            });
          },
        ),
      ),
    );
  }
}

Complete CRUD App in Flutter

//Want to support my work 🤝? https://buymeacoffee.com/vandad

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart'
    show getApplicationDocumentsDirectory;

class Person implements Comparable {
  final int id;
  final String firstName;
  final String lastName;
  const Person(this.id, this.firstName, this.lastName);

  String get fullName => '$firstName $lastName';

  Person.fromData(Map<String, Object?> data)
      : id = data['ID'] as int,
        firstName = data['FIRST_NAME'] as String,
        lastName = data['LAST_NAME'] as String;

  @override
  int compareTo(covariant Person other) => other.id.compareTo(id);

  @override
  bool operator ==(covariant Person other) => id == other.id;

  @override
  String toString() =>
      'Person, ID = $id, firstName = $firstName, lastName = $lastName';
}

class PersonDB {
  final _controller = StreamController<List<Person>>.broadcast();
  List<Person> _persons = [];
  Database? _db;
  final String dbName;
  PersonDB({required this.dbName});

  Future<bool> close() async {
    final db = _db;
    if (db == null) {
      return false;
    }
    await db.close();
    return true;
  }

  Future<bool> open() async {
    if (_db != null) {
      return true;
    }
    final directory = await getApplicationDocumentsDirectory();
    final path = '${directory.path}/$dbName';
    try {
      final db = await openDatabase(path);
      _db = db;

      // create the table if it doesn't exist

      final create = '''CREATE TABLE IF NOT EXISTS PEOPLE (
          ID INTEGER PRIMARY KEY AUTOINCREMENT,
          FIRST_NAME STRING NOT NULL,
          LAST_NAME STRING NOT NULL
        )''';

      await db.execute(create);

      // if everything went fine, we then read all the objects
      // and populate the stream

      _persons = await _fetchPeople();
      _controller.add(_persons);
      return true;
    } catch (e) {
      print('error = $e');
      return false;
    }
  }

  Future<List<Person>> _fetchPeople() async {
    final db = _db;
    if (db == null) {
      return [];
    }

    try {
      // read the existing data if any
      final readResult = await db.query(
        'PEOPLE',
        distinct: true,
        columns: ['ID', 'FIRST_NAME', 'LAST_NAME'],
        orderBy: 'ID',
      );

      final people = readResult.map((row) => Person.fromData(row)).toList();
      return people;
    } catch (e) {
      print('error = $e');
      return [];
    }
  }

  Future<bool> delete(Person person) async {
    final db = _db;
    if (db == null) {
      return false;
    }
    try {
      final deletedCount = await db.delete(
        'PEOPLE',
        where: 'ID = ?',
        whereArgs: [person.id],
      );

      // delete it locally as well

      if (deletedCount == 1) {
        _persons.remove(person);
        _controller.add(_persons);
        return true;
      } else {
        return false;
      }
    } catch (e) {
      print('Error inserting $e');
      return false;
    }
  }

  Future<bool> create(String firstName, String lastName) async {
    final db = _db;
    if (db == null) {
      return false;
    }
    try {
      final id = await db.insert(
        'PEOPLE',
        {
          'FIRST_NAME': firstName,
          'LAST_NAME': lastName,
        },
      );

      final person = Person(id, firstName, lastName);
      _persons.add(person);
      _controller.add(_persons);

      return true;
    } catch (e) {
      print('Error inserting $e');
      return false;
    }
  }

  // uses the person's id to update its first name and last name
  Future<bool> update(Person person) async {
    final db = _db;
    if (db == null) {
      return false;
    }
    try {
      final updatedCount = await db.update(
        'PEOPLE',
        {
          'FIRST_NAME': person.firstName,
          'LAST_NAME': person.lastName,
        },
        where: 'ID = ?',
        whereArgs: [person.id],
      );

      if (updatedCount == 1) {
        _persons.removeWhere((p) => p.id == person.id);
        _persons.add(person);
        _controller.add(_persons);
        return true;
      } else {
        return false;
      }
    } catch (e) {
      print('Error inserting $e');
      return false;
    }
  }

  Stream<List<Person>> all() =>
      _controller.stream.map((event) => event..sort());
}

typedef OnCompose = void Function(String firstName, String lastName);

class ComposeWidget extends StatefulWidget {
  final OnCompose onCompose;

  const ComposeWidget({Key? key, required this.onCompose}) : super(key: key);

  @override
  State<ComposeWidget> createState() => _ComposeWidgetState();
}

class _ComposeWidgetState extends State<ComposeWidget> {
  final firstNameController = TextEditingController();
  final lastNameController = TextEditingController();

  @override
  void dispose() {
    firstNameController.dispose();
    lastNameController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(children: [
        TextField(
          style: TextStyle(fontSize: 24),
          decoration: InputDecoration(
            hintText: 'Enter first name',
          ),
          controller: firstNameController,
        ),
        TextField(
          style: TextStyle(fontSize: 24),
          decoration: InputDecoration(
            hintText: 'Enter last name',
          ),
          controller: lastNameController,
        ),
        TextButton(
          onPressed: () {
            final firstName = firstNameController.text;
            final lastName = lastNameController.text;
            widget.onCompose(firstName, lastName);
          },
          child: Text(
            'Add to list',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ]),
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late final PersonDB _crudStorage;

  @override
  void initState() {
    _crudStorage = PersonDB(dbName: 'db.sqlite');
    _crudStorage.open();
    super.initState();
  }

  @override
  void dispose() {
    _crudStorage.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SQLite in Flutter'),
      ),
      body: StreamBuilder(
        stream: _crudStorage.all(),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.active:
            case ConnectionState.waiting:
              if (snapshot.data == null) {
                return Center(child: CircularProgressIndicator());
              }
              final people = snapshot.data as List<Person>;
              return Column(
                children: [
                  ComposeWidget(
                    onCompose: (firstName, lastName) async {
                      await _crudStorage.create(firstName, lastName);
                    },
                  ),
                  Expanded(
                    child: ListView.builder(
                      itemCount: people.length,
                      itemBuilder: (context, index) {
                        final person = people[index];
                        return ListTile(
                          onTap: () async {
                            final update =
                                await showUpdateDialog(context, person);
                            if (update == null) {
                              return;
                            }
                            await _crudStorage.update(update);
                          },
                          title: Text(
                            person.fullName,
                            style: TextStyle(fontSize: 24),
                          ),
                          subtitle: Text(
                            'ID: ${person.id}',
                            style: TextStyle(fontSize: 18),
                          ),
                          trailing: TextButton(
                            onPressed: () async {
                              final shouldDelete =
                                  await showDeleteDialog(context);
                              if (shouldDelete) {
                                await _crudStorage.delete(person);
                              }
                            },
                            child: Icon(
                              Icons.disabled_by_default_rounded,
                              color: Colors.red,
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                ],
              );
            default:
              return Center(child: CircularProgressIndicator());
          }
        },
      ),
    );
  }
}

final firstNameController = TextEditingController();
final lastNameController = TextEditingController();

Future<Person?> showUpdateDialog(BuildContext context, Person person) {
  firstNameController.text = person.firstName;
  lastNameController.text = person.lastName;
  return showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Enter your udpated values here:'),
            TextField(controller: firstNameController),
            TextField(controller: lastNameController),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop(null);
            },
            child: Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              final newPerson = Person(
                person.id,
                firstNameController.text,
                lastNameController.text,
              );
              Navigator.of(context).pop(newPerson);
            },
            child: Text('Save'),
          ),
        ],
      );
    },
  ).then((value) {
    if (value is Person) {
      return value;
    } else {
      return null;
    }
  });
}

Future<bool> showDeleteDialog(BuildContext context) {
  return showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        content: Text('Are you sure you want to delete this item?'),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop(false);
            },
            child: Text('No'),
          ),
          TextButton(
            onPressed: () {
              Navigator.of(context).pop(true);
            },
            child: Text('Delete'),
          )
        ],
      );
    },
  ).then(
    (value) {
      if (value is bool) {
        return value;
      } else {
        return false;
      }
    },
  );
}

SQLite Storage in Flutter



import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart'
    show getApplicationDocumentsDirectory;

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class Person implements Comparable {
  final int id;
  final String firstName;
  final String lastName;
  const Person(this.id, this.firstName, this.lastName);

  String get fullName => '$firstName $lastName';

  Person.fromData(Map<String, Object?> data)
      : id = data['ID'] as int,
        firstName = data['FIRST_NAME'] as String,
        lastName = data['LAST_NAME'] as String;

  @override
  int compareTo(covariant Person other) => other.id.compareTo(id);
}

typedef OnCompose = void Function(String firstName, String lastName);

class ComposeWidget extends StatefulWidget {
  final OnCompose onCompose;

  const ComposeWidget({Key? key, required this.onCompose}) : super(key: key);

  @override
  State<ComposeWidget> createState() => _ComposeWidgetState();
}

class _ComposeWidgetState extends State<ComposeWidget> {
  final firstNameController = TextEditingController();
  final lastNameController = TextEditingController();

  @override
  void dispose() {
    firstNameController.dispose();
    lastNameController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(children: [
        TextField(
          style: TextStyle(fontSize: 24),
          decoration: InputDecoration(
            hintText: 'Enter first name',
          ),
          controller: firstNameController,
        ),
        TextField(
          style: TextStyle(fontSize: 24),
          decoration: InputDecoration(
            hintText: 'Enter last name',
          ),
          controller: lastNameController,
        ),
        TextButton(
          onPressed: () {
            final firstName = firstNameController.text;
            final lastName = lastNameController.text;
            widget.onCompose(firstName, lastName);
          },
          child: Text(
            'Add to list',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ]),
    );
  }
}

class _HomePageState extends State<HomePage> {
  late final Database db;
  bool hasSetUpAlready = false;

  Future<bool> setupDatabase() async {
    if (hasSetUpAlready == false) {
      final directory = await getApplicationDocumentsDirectory();
      final path = '${directory.path}/db.sqlite';
      try {
        db = await openDatabase(path);

        // create the table if it doesn't exist

        final create = '''CREATE TABLE IF NOT EXISTS PEOPLE (
          ID INTEGER PRIMARY KEY AUTOINCREMENT,
          FIRST_NAME STRING NOT NULL,
          LAST_NAME STRING NOT NULL
        )''';

        await db.execute(create);
        hasSetUpAlready = true;
        return true;
      } catch (e) {
        print('error = $e');
        hasSetUpAlready = false;
        return false;
      }
    } else {
      return true;
    }
  }

  Future<List<Person>> fetchPersons() async {
    if (!await setupDatabase()) {
      return [];
    }

    try {
      // read the existing data if any
      final readResult = await db.query(
        'PEOPLE',
        distinct: true,
        columns: ['ID', 'FIRST_NAME', 'LAST_NAME'],
        orderBy: 'ID',
      );

      final people = readResult.map((row) => Person.fromData(row)).toList()
        ..sort();
      return people;
    } catch (e) {
      print('error = $e');
      return [];
    }
  }

  Future<bool> addPerson(String firstName, String lastName) async {
    try {
      await db.insert(
        'PEOPLE',
        {
          'FIRST_NAME': firstName,
          'LAST_NAME': lastName,
        },
      );
      return true;
    } catch (e) {
      print('Error inserting $e');
      return false;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SQLite in Flutter'),
      ),
      body: FutureBuilder(
        future: fetchPersons(),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.done:
              final people = snapshot.data as List<Person>;
              return Column(
                children: [
                  ComposeWidget(
                    onCompose: (firstName, lastName) async {
                      await addPerson(firstName, lastName);
                      setState(() {});
                    },
                  ),
                  Expanded(
                    child: ListView.builder(
                      itemCount: people.length,
                      itemBuilder: (context, index) {
                        final person = people[index];
                        return ListTile(
                          title: Text(
                            person.fullName,
                            style: TextStyle(fontSize: 24),
                          ),
                          subtitle: Text(
                            'ID: ${person.id}',
                            style: TextStyle(fontSize: 18),
                          ),
                        );
                      },
                    ),
                  ),
                ],
              );
            default:
              return CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

Circular Progress with Percentage in Flutter



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

class SizedCircularProgressIndicator extends StatelessWidget {
  final double progress;
  final double width;
  final double height;
  final TextStyle? textStyle;
  const SizedCircularProgressIndicator({
    Key? key,
    this.textStyle,
    required this.progress,
    required this.width,
    required this.height,
  }) : super(key: key);

  TextStyle get style => textStyle ?? TextStyle(fontSize: 30.0);
  int get _progress => (progress * 100.0).toInt();

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children: [
        Text('$_progress%', style: style),
        SizedBox(
          width: width,
          height: height,
          child: CircularProgressIndicator(
            backgroundColor: Colors.white70,
            value: progress,
            color: Colors.blueAccent,
            strokeWidth: 3.0,
          ),
        ),
      ],
    );
  }
}

const images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: images.length,
        itemBuilder: (context, index) {
          return WithBlurredBackground(imageUrl: images[index]);
        },
      ),
    );
  }
}

class WithBlurredBackground extends StatelessWidget {
  final String imageUrl;

  const WithBlurredBackground({Key? key, required this.imageUrl})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: Stack(
        alignment: Alignment.center,
        fit: StackFit.passthrough,
        children: [
          SizedBox.expand(
            child: ClipRect(
              child: ImageFiltered(
                imageFilter: ImageFilter.blur(
                  sigmaX: 10.0,
                  sigmaY: 10.0,
                ),
                child: Image.network(
                  imageUrl,
                  fit: BoxFit.fitHeight,
                ),
              ),
            ),
          ),
          NetworkImageWithProgress(url: imageUrl),
        ],
      ),
    );
  }
}

class NetworkImageWithProgress extends StatelessWidget {
  final String url;

  const NetworkImageWithProgress({Key? key, required this.url})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Image.network(
      url,
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return SizedCircularProgressIndicator(
            height: 100,
            width: 100,
            progress: bytesLoaded / totalBytes,
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );
  }
}

Opening URLs in Flutter



import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

class Person {
  final String profileUrl;
  final String name;
  final String email;
  final String phoneNumber;
  final String websiteUrl;

  const Person({
    Key? key,
    required this.profileUrl,
    required this.name,
    required this.email,
    required this.phoneNumber,
    required this.websiteUrl,
  });

  Person.vandad()
      : profileUrl = 'https://bit.ly/3jwusS0',
        name = 'Vandad Nahavandipoor',
        email = 'vandad.np@gmail.com',
        phoneNumber = '071234567',
        websiteUrl = 'https://pixolity.se';
}

void open(String url) async {
  if (await canLaunch(url)) {
    await launch(url);
  }
}

class PersonNameView extends StatelessWidget {
  final Person person;
  const PersonNameView(this.person, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      person.name,
      style: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.bold,
        color: Colors.black,
      ),
    );
  }
}

class PersonEmailView extends StatelessWidget {
  final Person person;
  const PersonEmailView(this.person, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () {
        open('mailto:${person.email}');
      },
      child: Text(
        '💌 Email me',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}

class PersonPhoneView extends StatelessWidget {
  final Person person;
  const PersonPhoneView(this.person, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () {
        open('tel://${person.phoneNumber}');
      },
      child: Text(
        '🤙🏻 Call me',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}

class PersonWebsiteView extends StatelessWidget {
  final Person person;
  const PersonWebsiteView(this.person, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () {
        open(person.websiteUrl);
      },
      child: Text(
        '🌍 Visit my website',
        style: TextStyle(fontSize: 20.0),
      ),
    );
  }
}

const bigText = TextStyle(fontSize: 20.0);

class PersonView extends StatelessWidget {
  final Person person;
  const PersonView({Key? key, required this.person}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
          color: Colors.blue[50],
          borderRadius: BorderRadius.circular(16.0),
          border: Border.all(color: Colors.white, width: 3.0),
          boxShadow: [
            BoxShadow(
              blurRadius: 30.0,
              color: Colors.black.withAlpha(50),
              spreadRadius: 20.0,
            ),
          ]),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircleAvatar(
              radius: 100.0,
              backgroundImage: NetworkImage(person.profileUrl),
            ),
            SizedBox(height: 10),
            PersonNameView(person),
            PersonEmailView(person),
            PersonPhoneView(person),
            PersonWebsiteView(person)
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          child: PersonView(
            person: Person.vandad(),
          ),
        ),
      ),
    );
  }
}

Commodore 64 Screen in Flutter



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

const textColor = Color.fromRGBO(156, 156, 247, 1);

class BoxPainter extends CustomPainter {
  final bool isBlinking;
  BoxPainter({required this.isBlinking});

  @override
  void paint(Canvas canvas, Size size) {
    if (isBlinking) {
      final rect = Rect.fromLTWH(
        size.width / 20.0,
        size.height / 2.8,
        size.width / 24.0,
        size.height / 13.0,
      );
      final paint = Paint()..color = textColor;
      canvas.drawRect(rect, paint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

class ReadyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final textStyle = TextStyle(
      color: textColor,
      fontSize: size.width / 45.0,
      fontFamily: 'C64',
    );
    final span = TextSpan(
      text: 'READY',
      style: textStyle,
    );
    final painter = TextPainter(
      text: span,
      textDirection: TextDirection.ltr,
    );

    painter.layout(
      minWidth: 0,
      maxWidth: size.width,
    );
    final offset = Offset(
      painter.width / 2.0,
      painter.height * 8.0,
    );
    painter.paint(canvas, offset);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

class SubTitlePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final textStyle = TextStyle(
      color: textColor,
      fontSize: size.width / 45.0,
      fontFamily: 'C64',
    );
    final span = TextSpan(
      text: '64K RAM SYSTEM 38911 BASIC BYTES FREE',
      style: textStyle,
    );
    final painter = TextPainter(
      text: span,
      textDirection: TextDirection.ltr,
    );

    painter.layout(
      minWidth: 0,
      maxWidth: size.width,
    );
    final offset = Offset(
      size.width - painter.width - (size.width / 11),
      painter.height * 6.0,
    );
    painter.paint(canvas, offset);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

class TitlePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final textStyle = TextStyle(
      color: textColor,
      fontSize: size.width / 40.0,
      fontFamily: 'C64',
    );
    final span = TextSpan(
      text: '**** COMMODORE 64 BASIC V2 ****',
      style: textStyle,
    );
    final painter = TextPainter(
      text: span,
      textDirection: TextDirection.ltr,
    );

    painter.layout(
      minWidth: 0,
      maxWidth: size.width,
    );
    final offset = Offset(
      size.width - painter.width - (size.width / 9),
      size.height / 6,
    );
    painter.paint(canvas, offset);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

class BackgroundPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final border = size.width / 20.0;

    final color = Color.fromRGBO(
      58,
      57,
      213,
      1,
    );

    final paint = Paint()..color = color;

    final rect = Rect.fromLTWH(
      border,
      border,
      size.width - (border * 2.0),
      size.height - (border * 2.0),
    );

    canvas.drawRect(rect, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

class BorderPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = textColor;
    final rect = Rect.fromLTWH(
      0.0,
      0.0,
      size.width,
      size.height,
    );

    canvas.drawRect(rect, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

class Commodore64Painter extends CustomPainter {
  final bool isBlinking;

  late final List<CustomPainter> painters;

  Commodore64Painter({required this.isBlinking}) {
    painters = [
      BorderPainter(),
      BackgroundPainter(),
      TitlePainter(),
      SubTitlePainter(),
      ReadyPainter(),
      BoxPainter(isBlinking: isBlinking)
    ];
  }

  @override
  void paint(Canvas canvas, Size size) {
    painters.forEach(
      (p) => p.paint(
        canvas,
        size,
      ),
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => painters
      .map((p) => p.shouldRepaint(oldDelegate))
      .reduce((p1, p2) => p1 || p2);
}

class Commodore64 extends StatefulWidget {
  const Commodore64({Key? key}) : super(key: key);

  @override
  State<Commodore64> createState() => _Commodore64State();
}

class _Commodore64State extends State<Commodore64> {
  bool _isBlinking = false;

  final timer = Stream.periodic(Duration(seconds: 1), (value) => value);

  void _triggerRedraw() async {
    await for (final _ in timer) {
      setState(() {
        _isBlinking = !_isBlinking;
      });
    }
  }

  @override
  void initState() {
    super.initState();
    _triggerRedraw();
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      return Container(
        width: constraints.maxWidth,
        height: constraints.maxWidth / (16.0 / 9.0),
        child: CustomPaint(
          painter: Commodore64Painter(isBlinking: _isBlinking),
        ),
      );
    });
  }
}

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

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

Animated Lists in Flutter



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

class NetworkImage extends StatelessWidget {
  final String url;
  const NetworkImage({
    Key? key,
    required this.url,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Image.network(
      url,
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return LinearProgressIndicator(
            value: bytesLoaded / totalBytes,
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );
  }
}

class NetworkImageCard extends StatelessWidget {
  final String url;
  const NetworkImageCard({
    Key? key,
    required this.url,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(20.0),
        ),
        child: NetworkImage(
          url: url,
        ),
      ),
    );
  }
}

const imageUrls = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

Stream<String> images() => Stream.periodic(
      Duration(seconds: 3),
      (index) => index % imageUrls.length,
    ).map((index) => imageUrls[index]);

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final GlobalKey<AnimatedListState> _key = GlobalKey();

  List<String> imageUrls = [];

  void populateList() async {
    await for (final url in images()) {
      imageUrls.insert(0, url);
      _key.currentState?.insertItem(
        0,
        duration: Duration(milliseconds: 400),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    populateList();
    return Scaffold(
      appBar: AppBar(
        title: Text('Animated Lists in Flutter'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: AnimatedList(
          key: _key,
          initialItemCount: imageUrls.length,
          itemBuilder: (context, index, animation) {
            final imageUrl = imageUrls[index];
            return SizeTransition(
              sizeFactor: animation.drive(
                CurveTween(curve: Curves.elasticInOut),
              ),
              child: Column(
                children: [
                  NetworkImageCard(url: imageUrl),
                  SizedBox(height: 10.0),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

CheckboxListTile in Flutter

import 'package:flutter/material.dart';



enum Item { umbrella, coat, boots }

extension Info on Item {
  String get title {
    switch (this) {
      case Item.umbrella:
        return 'Umbrella';
      case Item.boots:
        return 'Boots';
      case Item.coat:
        return 'Jacket';
    }
  }

  String get icon {
    switch (this) {
      case Item.umbrella:
        return '☂️';
      case Item.boots:
        return '🥾';
      case Item.coat:
        return '🧥';
    }
  }
}

typedef OnChecked = void Function(bool);

class Tile extends StatelessWidget {
  final Item item;
  final bool isChecked;
  final OnChecked onChecked;
  const Tile({
    Key? key,
    required this.item,
    required this.isChecked,
    required this.onChecked,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: BoxConstraints(minHeight: 100.0),
      child: Center(
        child: CheckboxListTile(
          shape: ContinuousRectangleBorder(),
          value: isChecked,
          secondary: Text(
            item.icon,
            style: TextStyle(fontSize: 30.0),
          ),
          title: Text(
            item.title,
            style: TextStyle(fontSize: 40.0),
          ),
          onChanged: (value) {
            onChecked(value ?? false);
          },
        ),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final Set<Item> _enabledItems = {};

  Widget get listView {
    return ListView.builder(
      shrinkWrap: true,
      itemBuilder: (context, index) {
        final item = Item.values[index];
        final isChecked = _enabledItems.contains(item);
        return Tile(
          isChecked: isChecked,
          item: item,
          onChecked: (isChecked) {
            setState(() {
              if (isChecked) {
                _enabledItems.add(item);
              } else {
                _enabledItems.remove(item);
              }
            });
          },
        );
      },
      itemCount: Item.values.length,
    );
  }

  Widget get title {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Text(
        "Remember to pick all items! It's going to be rainy today!",
        textAlign: TextAlign.center,
        style: TextStyle(fontSize: 40),
      ),
    );
  }

  Widget get readyButton {
    final onPressed = () async {
      // program this
      await showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            content: Text('You seem to be ready for a rainy day! ✅'),
            actions: [
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text('OK'),
              )
            ],
          );
        },
      );
    };

    final isEnabled = _enabledItems.containsAll(Item.values);

    return FractionallySizedBox(
      widthFactor: 0.8,
      child: ElevatedButton(
        onPressed: isEnabled ? onPressed : null,
        child: Text('Ready!'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Checkbox List Tile in Flutter'),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            title,
            listView,
            SizedBox(height: 50.0),
            readyButton,
          ],
        ),
      ),
    );
  }
}

- Operator on String in Dart



extension Minus on String {
  String operator -(String rhs) => replaceAll(rhs, '');
}

void testIt() {
  assert('foo bar' - 'foo ' == 'bar');
  assert('foo bar foo' - 'foo' == ' bar ');
  assert('bar' - 'foo' == 'bar');
  assert('BAR' - 'bar' == 'BAR');
  assert('foo' - 'FOO' == 'foo');
  assert('foobarbaz' - 'bar' == 'foobaz');
}

Dart Progress for Future<T>



import 'dart:io' show stdout;
import 'dart:async' show Future, Stream;

const loadingSequence = ['⢿', '⣻', '⣽', '⣾', '⣷', '⣯', '⣟', '⡿'];
const escape = '\x1B[38;5;';
const color = '${escape}1m';
const textColor = '${escape}6m';

String progress({required int value, required String text}) {
  final progress = '$color${loadingSequence[value % loadingSequence.length]}';
  final coloredText = '$textColor$text';
  return '$progress\t$coloredText';
}

Future<T> performWithProgress<T>({
  required Future<T> task,
  required String progressText,
}) {
  final stream = Stream<String>.periodic(
    Duration(milliseconds: 100),
    (value) => progress(
      value: value,
      text: progressText,
    ),
  );

  final subscription = stream.listen(
    (event) {
      stdout.write('\r $event');
    },
  );

  task.whenComplete(() {
    stdout.write('\r ✅\t$progressText');
    stdout.write('\n');
    subscription.cancel();
  });

  return task;
}

final task1 = Future.delayed(Duration(seconds: 1), () => 'Result 1');
final task2 = Future.delayed(Duration(seconds: 2), () => 'Result 2');
final task3 = Future.delayed(Duration(seconds: 3), () => 'Result 3');

void main(List<String> args) async {
  var result = await performWithProgress(
    task: task1,
    progressText: 'Loading task 1',
  );
  print('\tTask 1 result: $result');
  result = await performWithProgress(
    task: task2,
    progressText: 'Loading task 2',
  );
  print('\tTask 2 result: $result');
  result = await performWithProgress(
    task: task3,
    progressText: 'Loading task 3',
  );
  print('\tTask 3 result: $result');
}

Move Widget Shadows with Animation



import 'package:flutter/material.dart';

class ImageTransition extends AnimatedWidget {
  final String imageUrl;

  Animation<double> get shadowXOffset => listenable as Animation<double>;

  const ImageTransition(this.imageUrl, {shadowXOffset})
      : super(listenable: shadowXOffset);

  @override
  Widget build(BuildContext context) {
    return Container(
      clipBehavior: Clip.antiAlias,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20.0),
        boxShadow: [
          BoxShadow(
            blurRadius: 10,
            offset: Offset(shadowXOffset.value, 20.0),
            color: Colors.black.withAlpha(100),
            spreadRadius: -10,
          )
        ],
      ),
      child: Image.network(imageUrl),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class CustomCurve extends CurveTween {
  CustomCurve() : super(curve: Curves.easeInOutSine);
  @override
  double transform(double t) {
    return (super.transform(t) - 0.5) * 25.0;
  }
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );
    _animation = CustomCurve().animate(_controller);
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    _controller.repeat(reverse: true);
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: ImageTransition(
            'https://bit.ly/3x7J5Qt',
            shadowXOffset: _animation,
          ),
        ),
      ),
    );
  }
}

Gallery with Blurred Backgrounds in Flutter


import 'package:flutter/material.dart';

const images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: images.length,
        itemBuilder: (context, index) {
          return WithBlurredBackground(imageUrl: images[index]);
        },
      ),
    );
  }
}

class WithBlurredBackground extends StatelessWidget {
  final String imageUrl;

  const WithBlurredBackground({Key? key, required this.imageUrl})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: Stack(
        alignment: Alignment.center,
        fit: StackFit.passthrough,
        children: [
          SizedBox.expand(
            child: ClipRect(
              child: ImageFiltered(
                imageFilter: ImageFilter.blur(
                  sigmaX: 10.0,
                  sigmaY: 10.0,
                ),
                child: Image.network(
                  imageUrl,
                  fit: BoxFit.fitHeight,
                ),
              ),
            ),
          ),
          Image.network(imageUrl),
        ],
      ),
    );
  }
}

Custom Path Clippers in Flutter



import 'package:flutter/material.dart';

const images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.only(
            top: 40.0,
            left: 10.0,
            right: 10.0,
          ),
          child: Column(
            children: images
                .map((url) => ElevatedNetworkImage(url: url))
                .expand(
                  (img) => [
                    img,
                    SizedBox(height: 30.0),
                  ],
                )
                .toList(),
          ),
        ),
      ),
    );
  }
}

class ElevatedNetworkImage extends StatelessWidget {
  final String url;
  const ElevatedNetworkImage({Key? key, required this.url}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PhysicalShape(
      color: Colors.white,
      clipper: Clipper(),
      elevation: 20.0,
      clipBehavior: Clip.none,
      shadowColor: Colors.white.withAlpha(200),
      child: CutEdges(
        child: Image.network(url),
      ),
    );
  }
}

class Clipper extends CustomClipper<Path> {
  static const variance = 0.2;
  static const reverse = 1.0 - variance;

  @override
  Path getClip(Size size) {
    final path = Path();

    path.moveTo(0.0, size.height * Clipper.variance);
    path.lineTo(size.width * Clipper.variance, 0.0);
    path.lineTo(size.width, 0.0);
    path.lineTo(size.width, size.height * Clipper.reverse);
    path.lineTo(size.width * Clipper.reverse, size.height);
    path.lineTo(0.0, size.height);
    path.lineTo(0.0, size.height * Clipper.variance);
    path.close;
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

class CutEdges extends StatelessWidget {
  final Widget child;

  const CutEdges({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: Clipper(),
      child: child,
    );
  }
}

Frost Effect on Images in Flutter



import 'package:flutter/material.dart';

const images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

final loremIpsum =
    'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.fromLTRB(
            8.0,
            0.0,
            8.0,
            0.0,
          ),
          child: SingleChildScrollView(
            child: Column(
              children: images
                  .map(
                    (url) => GlossyNetworkImageWithProgress(
                      url: url,
                      title: 'Image title',
                      description: loremIpsum,
                    ),
                  )
                  .expand(
                    (image) => [
                      image,
                      SizedBox(height: 16.0),
                    ],
                  )
                  .toList(),
            ),
          ),
        ),
      ),
    );
  }
}

class GlossyNetworkImageWithProgress extends StatefulWidget {
  final String url;
  final String title;
  final String description;

  const GlossyNetworkImageWithProgress(
      {Key? key,
      required this.url,
      required this.title,
      required this.description})
      : super(key: key);

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

class _GlossyNetworkImageWithProgressState
    extends State<GlossyNetworkImageWithProgress>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );

    _animation = Tween(
      begin: 0.0,
      end: 1.0,
    ).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    final networkImage = Image.network(
      widget.url,
      fit: BoxFit.fitHeight,
      frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
        _controller.reset();
        _controller.forward();
        return FadeTransition(
          opacity: _animation,
          child: CustomBox(
            child: child,
          ),
        );
      },
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return CustomBox(
            child: CircularProgressIndicator(
              backgroundColor: Colors.white70,
              value: bytesLoaded / totalBytes,
              color: Colors.blue[900],
              strokeWidth: 5.0,
            ),
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );

    return BottomGloss(
      networkImage: networkImage,
      title: widget.title,
      description: widget.description,
    );
  }
}

class BottomGloss extends StatelessWidget {
  final String title;
  final String description;

  const BottomGloss(
      {Key? key,
      required this.networkImage,
      required this.title,
      required this.description})
      : super(key: key);

  final Image networkImage;

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.all(Radius.circular(8.0)),
      child: Stack(
        fit: StackFit.passthrough,
        children: [
          networkImage,
          Container(
            height: 300.0,
            alignment: Alignment.bottomCenter,
            child: ClipRect(
              child: FractionallySizedBox(
                heightFactor: 0.5,
                child: BackdropFilter(
                  filter: ImageFilter.blur(
                    sigmaX: 10.0,
                    sigmaY: 10.0,
                  ),
                  child: BottomContents(
                    title: title,
                    description: description,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class BottomContents extends StatelessWidget {
  final String title;
  final String description;

  const BottomContents({
    Key? key,
    required this.title,
    required this.description,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white.withOpacity(0.4),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              TitleText(text: title),
              SizedBox(height: 8.0),
              SubTitleText(text: description),
            ],
          ),
        ),
      ),
    );
  }
}

class SubTitleText extends StatelessWidget {
  final String text;
  const SubTitleText({Key? key, required this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: TextStyle(
        color: Colors.black,
        fontSize: 20.0,
      ),
    );
  }
}

class TitleText extends StatelessWidget {
  final String text;
  const TitleText({Key? key, required this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: TextStyle(
        color: Colors.white,
        fontSize: 30.0,
      ),
    );
  }
}

class CustomBox extends StatelessWidget {
  final Widget child;

  const CustomBox({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 300.0,
      width: MediaQuery.of(context).size.width,
      child: child is ProgressIndicator ? Center(child: child) : child,
    );
  }
}

Custom Clippers in Flutter

import 'package:flutter/material.dart';
import 'dart:math' show min;

const gridImages = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/3jRSRCu',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GridView.count(
        padding: const EdgeInsets.fromLTRB(8.0, 48.0, 8.0, 48.0),
        crossAxisCount: 2,
        mainAxisSpacing: 8.0,
        crossAxisSpacing: 8.0,
        children: gridImages
            .map((url) => NetworkImageWithProgress(url: url))
            .toList(),
      ),
    );
  }
}

class CircularClipper extends CustomClipper<Rect> {
  @override
  Rect getClip(Size size) {
    final center = Offset(
      size.width / 2.0,
      size.height / 2.0,
    );
    final minWidthorHeight = min(size.width, size.height);
    return Rect.fromCenter(
      center: center,
      width: minWidthorHeight,
      height: minWidthorHeight,
    );
  }

  @override
  bool shouldReclip(covariant CustomClipper<Rect> oldClipper) => false;
}

class Circular extends StatelessWidget {
  final Widget child;
  const Circular({Key? key, required this.child}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return ClipOval(
      clipper: CircularClipper(),
      child: child,
    );
  }
}

class CustomBox extends StatelessWidget {
  final Widget child;

  const CustomBox({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 220.0,
      width: MediaQuery.of(context).size.width,
      child: child is ProgressIndicator
          ? Center(child: child)
          : Circular(child: child),
    );
  }
}

class NetworkImageWithProgress extends StatefulWidget {
  final String url;

  const NetworkImageWithProgress({Key? key, required this.url})
      : super(key: key);

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

class _NetworkImageWithProgressState extends State<NetworkImageWithProgress>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );

    _animation = Tween(
      begin: 0.0,
      end: 1.0,
    ).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Image.network(
      widget.url,
      fit: BoxFit.fitHeight,
      frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
        _controller.reset();
        _controller.forward();
        return FadeTransition(
          opacity: _animation,
          child: CustomBox(
            child: child,
          ),
        );
      },
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return CustomBox(
            child: CircularProgressIndicator(
              backgroundColor: Colors.white70,
              value: bytesLoaded / totalBytes,
              color: Colors.blue[900],
              strokeWidth: 5.0,
            ),
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );
  }
}

Check if Website is Up or Down in Dart



class UpStatus {
  final bool isUp;
  final DateTime timestamp;
  const UpStatus(this.isUp, this.timestamp);
}

class Pling {
  final String url;
  final Duration interval;
  const Pling({
    required this.url,
    required this.interval,
  });
  Stream<UpStatus> checkIfUp() =>
      Stream.periodic(interval, (_) => DateTime.now()).asyncExpand(
        (now) => HttpClient()
            .headUrl(Uri.parse(url))
            .then((req) => req..followRedirects = false)
            .then((req) => req.close())
            .then((resp) => resp.statusCode)
            .then((statusCode) => statusCode == 200)
            .onError((error, stackTrace) => false)
            .then((isUp) => UpStatus(isUp, now))
            .asStream(),
      );
}

const oneSecond = Duration(seconds: 1);
const url = 'https://dart.dev';

extension IsOrIsNot on bool {
  String get isOrIsNot => this ? 'is' : 'is not';
}

void testIt() async {
  final pling = Pling(
    url: url,
    interval: oneSecond,
  );
  await for (final upStatus in pling.checkIfUp()) {
    final timestamp = upStatus.timestamp;
    final isUpStr = upStatus.isUp.isOrIsNot;
    print('$url $isUpStr up at $timestamp');
  }
}

Section Titles on ListView in Flutter

import 'package:flutter/material.dart';

final List<Section> allSections = [
  Section(
    'Spring',
    [
      'https://cnn.it/3xu58Ap',
      'https://bit.ly/3ueqqC1',
    ],
  ),
  Section(
    'Summer',
    [
      'https://bit.ly/3ojNhLc',
      'https://bit.ly/2VcCSow',
    ],
  ),
  Section(
    'Autumn',
    [
      'https://bit.ly/3ib1TJk',
      'https://bit.ly/2XSpjvq',
    ],
  ),
  Section(
    'Winter',
    [
      'https://bit.ly/3iaQNE7',
      'https://bit.ly/3AY8YE4',
    ],
  ),
];

class Section {
  final String title;
  final List<String> urls;
  const Section(this.title, this.urls);
}

extension ToWidgets on Section {
  Iterable<Widget> toNetworkImageCards() {
    return [
      Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(
          title,
          style: TextStyle(
            fontSize: 40,
          ),
        ),
      ),
      ...urls.expand(
        (url) => [
          NetworkImageCard(url: url),
          SizedBox(height: 10),
        ],
      ),
    ];
  }
}

class NetworkImageCard extends StatelessWidget {
  final String url;
  const NetworkImageCard({
    Key? key,
    required this.url,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: NetworkImageWithProgress(
          url: url,
        ),
      ),
    );
  }
}

class NetworkImageWithProgress extends StatefulWidget {
  final String url;

  const NetworkImageWithProgress({Key? key, required this.url})
      : super(key: key);

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

class _NetworkImageWithProgressState extends State<NetworkImageWithProgress>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );

    _animation = Tween(
      begin: 0.0,
      end: 1.0,
    ).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Image.network(
      widget.url,
      fit: BoxFit.fitWidth,
      frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
        _controller.reset();
        _controller.forward();
        return FadeTransition(
          opacity: _animation,
          child: CustomBox(
            child: child,
          ),
        );
      },
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return CustomBox(
            child: CircularProgressIndicator(
              backgroundColor: Colors.white70,
              value: bytesLoaded / totalBytes,
              color: Colors.blue[900],
              strokeWidth: 5.0,
            ),
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );
  }
}

class CustomBox extends StatelessWidget {
  final Widget child;

  const CustomBox({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 220.0,
      width: MediaQuery.of(context).size.width,
      child: child is ProgressIndicator ? Center(child: child) : child,
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemBuilder: (context, index) {
          final section = allSections[index];
          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: section.toNetworkImageCards().toList(),
          );
        },
        itemCount: allSections.length,
      ),
    );
  }
}

Circular Progress in Flutter



import 'package:flutter/material.dart';

class CustomBox extends StatelessWidget {
  final Widget child;

  const CustomBox({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 220.0,
      width: MediaQuery.of(context).size.width,
      child: Center(child: child),
    );
  }
}

class NetworkImageWithProgress extends StatefulWidget {
  final String url;

  const NetworkImageWithProgress({Key? key, required this.url})
      : super(key: key);

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

class _NetworkImageWithProgressState extends State<NetworkImageWithProgress>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );

    _animation = Tween(
      begin: 0.0,
      end: 1.0,
    ).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Image.network(
      widget.url,
      fit: BoxFit.fitWidth,
      frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
        _controller.reset();
        _controller.forward();
        return FadeTransition(
          opacity: _animation,
          child: CustomBox(
            child: child,
          ),
        );
      },
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return CustomBox(
            child: CircularProgressIndicator(
              backgroundColor: Colors.white70,
              value: bytesLoaded / totalBytes,
              color: Colors.blue[900],
              strokeWidth: 5.0,
            ),
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );
  }
}

final images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3ywzOla',
].map((url) => NetworkImageWithProgress(url: url)).expand(
      (element) => [
        element,
        SizedBox(
          height: 10.0,
        )
      ],
    );

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(
          children: images.toList(),
        ),
      ),
    );
  }
}

Displaying Scroll Wheels in Flutter



import 'package:flutter/material.dart';

class FadingNetworkImage extends StatefulWidget {
  final String url;

  const FadingNetworkImage({Key? key, required this.url}) : super(key: key);

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

class _FadingNetworkImageState extends State<FadingNetworkImage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    _controller =
        AnimationController(vsync: this, duration: Duration(seconds: 1));

    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Image.network(
      widget.url,
      frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
        _controller.reset();
        _controller.forward();
        return FadeTransition(
          opacity: _animation,
          child: child,
        );
      },
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return LinearProgressIndicator(
            value: bytesLoaded / totalBytes,
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );
  }
}

final images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3ywzOla',
].map((i) => NetworkImageCard(url: i));

class NetworkImageCard extends StatelessWidget {
  final String url;
  const NetworkImageCard({
    Key? key,
    required this.url,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(20.0),
          boxShadow: [
            BoxShadow(
              blurRadius: 5,
              offset: Offset(0, 0),
              color: Colors.black.withAlpha(40),
              spreadRadius: 5,
            )
          ],
        ),
        child: FadingNetworkImage(
          url: url,
        ),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListWheelScrollView(
        itemExtent: 164.0,
        squeeze: 0.9,
        perspective: 0.003,
        children: images.toList(),
      ),
    );
  }
}

Post Messages to Slack with Dart



import 'dart:convert' show utf8;
import 'dart:convert' show json;

class SlackMessage {
  final String? inChannel;
  final String? userName;
  final String message;
  final String? iconEmoji;

  const SlackMessage({
    required this.inChannel,
    required this.userName,
    required this.message,
    required this.iconEmoji,
  });

  Future<bool> send(String webhookUrl) async {
    final payload = {
      'text': message,
      if (inChannel != null) 'channel': inChannel!,
      if (userName != null) 'username': userName!,
      if (iconEmoji != null) 'icon_emoji': iconEmoji!
    };

    final request = await HttpClient().postUrl(Uri.parse(webhookUrl));
    final payloadData = utf8.encode(json.encode(payload));
    request.add(payloadData);
    final response = await request.close();
    return response.statusCode == 200;
  }
}

const webhookUrl = 'put your webhook url here';

void testIt() async {
  final message = SlackMessage(
    inChannel: 'dart',
    userName: 'Flutter',
    message: 'Hello from Dart in Terminal',
    iconEmoji: 'blue_heart:',
  );
  if (await message.send(webhookUrl)) {
    print('Successfully sent the message');
  } else {
    print('Could not send the message');
  }
}

Unwrap List<T?>? in Dart



extension Unwrap<T> on List<T?>? {
  List<T> unwrap() => (this ?? []).whereType<T>().toList();
}

void testOptionalListOfOptionals() {
  final List<int?>? optionalListOfOptionals = [1, 2, null, 3, null];
  final unwrapped = optionalListOfOptionals.unwrap(); // List<int>
  print(unwrapped); // prints [1, 2, 3]
}

void testListOfOptionals() {
  final listOfOptionals = [20, 30, null, 40]; // List<int?>
  final unwrapped = listOfOptionals.unwrap(); // List<int>
  print(unwrapped); // prints [20, 30, 40]
}

void testNormalList() {
  final list = [50, 60, 70]; // List<int>
  final unwrapped = list.unwrap(); // List<int>
  print(unwrapped); // prints [50, 60, 70]
}

Avoiding UI Jitters When Switching Widgets in Flutter



const imageUrls = [
  'https://cnn.it/3xu58Ap', // spring
  'https://bit.ly/2VcCSow', // summer
  'https://bit.ly/3A3zStC', // autumn
  'https://bit.ly/2TNY7wi' // winter
];

extension ToNetworkImage<T extends String> on List<T> {
  List<Widget> toNetworkImages() => map((s) => Image.network(s)).toList();
}

class HomePage extends StatefulWidget {
  @override
  State createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  var _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Indexed Stack')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IndexedStack(
              index: _currentIndex,
              children: imageUrls.toNetworkImages(),
            ),
            TextButton(
              onPressed: () {
                setState(
                  () {
                    _currentIndex++;
                    if (_currentIndex >= imageUrls.length) {
                      _currentIndex = 0;
                    }
                  },
                );
              },
              child: Text('Go to next season'),
            )
          ],
        ),
      ),
    );
  }
}

Detect Redirects in Dart



Future<bool> doesRedirect(String url) => HttpClient()
    .headUrl(Uri.parse(url))
    .then((req) => req..followRedirects = false)
    .then((req) => req.close())
    .then((resp) => resp.statusCode)
    .then((statusCode) => [301, 302, 303, 307, 308].contains(statusCode));

void testIt() async {
  final test1 = await doesRedirect('https://cnn.it/3xu58Ap');
  assert(test1 == true);

  final test2 = await doesRedirect('https://dart.dev');
  assert(test2 == false);

  final test3 = await doesRedirect('https://bit.ly/2VcCSow');
  assert(test3 == true);
}

Proportional Constraints in Flutter



class ProportionalWidthNetworkImage extends StatelessWidget {
  final String url;
  final double widthProportion;

  const ProportionalWidthNetworkImage(
      {Key? key, required this.url, required this.widthProportion})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return Image.network(
          url,
          loadingBuilder: (context, child, loadingProgress) {
            final widget =
                loadingProgress == null ? child : LinearProgressIndicator();
            return Container(
              width: constraints.maxWidth * widthProportion,
              child: widget,
            );
          },
        );
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ProportionalWidthNetworkImage(
          url: 'https://cnn.it/3xu58Ap',
          widthProportion: 0.8,
        ),
      ),
    );
  }
}

Displaying Cupertino Action Sheets in Flutter



import 'package:flutter/cupertino.dart';

enum Season { spring, summer, autumn, winter }

extension Title on Season {
  String get title => describeEnum(this).capitalized;
}

extension Caps on String {
  String get capitalized => this[0].toUpperCase() + substring(1);
}

extension ToWidget on Season {
  Widget toWidget() {
    switch (this) {
      case Season.spring:
        return Image.network('https://cnn.it/3xu58Ap');
      case Season.summer:
        return Image.network('https://bit.ly/2VcCSow');
      case Season.autumn:
        return Image.network('https://bit.ly/3A3zStC');
      case Season.winter:
        return Image.network('https://bit.ly/2TNY7wi');
    }
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key? key}) : super(key: key);

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

Future<Season> _chooseSeason(
  BuildContext context,
  Season currentSeason,
) async {
  CupertinoActionSheet actionSheet(BuildContext context) {
    return CupertinoActionSheet(
      title: Text('Choose your favorite season:'),
      actions: Season.values
          .map(
            (season) => CupertinoActionSheetAction(
              onPressed: () {
                Navigator.of(context).pop(season);
              },
              child: Text(season.title),
            ),
          )
          .toList(),
      cancelButton: CupertinoActionSheetAction(
        onPressed: () {
          Navigator.of(context).pop(currentSeason);
        },
        child: Text('Cancel'),
      ),
    );
  }

  return await showCupertinoModalPopup(
    context: context,
    builder: (context) => actionSheet(context),
  );
}

class _HomePageState extends State<HomePage> {
  var _season = Season.spring;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_season.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          _season.toWidget(),
          TextButton(
            onPressed: () async {
              _season = await _chooseSeason(
                context,
                _season,
              );
              setState(() {});
            },
            child: Text('Choose a season'),
          ),
        ],
      ),
    );
  }
}

Rotating List<T> in Dart



extension Rotate<T> on List<T> {
  int _rotationTimes(int places) {
    if (isEmpty) {
      return 0;
    }
    if (places == 0) {
      throw ArgumentError('places should be more than 0');
    }
    return places % length;
  }

  List<T> rotatedRight(int places) {
    final times = _rotationTimes(places);
    if (times == 0) {
      return this;
    } else {
      final cutOff = length - times;
      return sublist(cutOff)..addAll(sublist(0, cutOff));
    }
  }

  List<T> rotatedLeft(int places) {
    final times = _rotationTimes(places);
    if (times == 0) {
      return this;
    } else {
      return sublist(times)..addAll(sublist(0, times));
    }
  }
}

extension Equality<T extends Comparable> on List<T> {
  bool isEqualTo(List<T> other) {
    if (other.length != length) {
      return false;
    }
    for (var i = 0; i < length; i++) {
      if (other[i] != this[i]) {
        return false;
      }
    }
    return true;
  }
}

const arr = [1, 2, 3];

void testIt() {
  assert(arr.rotatedRight(1).isEqualTo([3, 1, 2]));
  assert(arr.rotatedRight(2).isEqualTo([2, 3, 1]));
  assert(arr.rotatedRight(3).isEqualTo([1, 2, 3]));
  assert(arr.rotatedRight(4).isEqualTo([3, 1, 2]));
  assert(arr.rotatedLeft(1).isEqualTo([2, 3, 1]));
  assert(arr.rotatedLeft(2).isEqualTo([3, 1, 2]));
  assert(arr.rotatedLeft(3).isEqualTo([1, 2, 3]));
  assert(arr.rotatedLeft(4).isEqualTo([2, 3, 1]));
}

Displaying SnackBars in Flutter



class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hello world'),
      ),
      body: Center(
        child: TextButton(
          onPressed: () {
            final now = DateFormat('kk:mm:ss').format(DateTime.now());
            ScaffoldMessenger.of(context).removeCurrentSnackBar();
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                behavior: SnackBarBehavior.floating,
                elevation: 5.0,
                backgroundColor:
                    Colors.blue[600]!.withOpacity(0.8).withAlpha(200),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                  side: BorderSide(
                    color: Colors.black.withOpacity(0.4),
                    width: 3.0,
                  ),
                ),
                content: Text('Some text $now'),
              ),
            );
          },
          child: Text('Show toast'),
        ),
      ),
    );
  }
}

Custom Tab Bar Using ToggleButtons in Flutter



class TabBarButton extends StatelessWidget {
  final IconData icon;
  final double size;

  const TabBarButton({Key? key, required this.icon, this.size = 60.0})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Icon(
        icon,
        size: size,
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Toggle Buttons'),
      ),
      body: Column(
        children: [
          CustomTabBar(),
        ],
      ),
    );
  }
}

class CustomTabBar extends StatefulWidget {
  const CustomTabBar({Key? key}) : super(key: key);

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

class _CustomTabBarState extends State<CustomTabBar> {
  var _selection = [false, false, false];

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Align(
        alignment: FractionalOffset.bottomCenter,
        child: SafeArea(
          child: ToggleButtons(
            isSelected: _selection,
            onPressed: (index) {
              setState(() {
                _selection = List.generate(
                  _selection.length,
                  (i) => index == i ? true : false,
                );
              });
            },
            selectedColor: Colors.white,
            fillColor: Colors.blue,
            borderRadius: BorderRadius.circular(10.0),
            borderWidth: 4.0,
            borderColor: Colors.blue[400],
            selectedBorderColor: Colors.blue,
            highlightColor: Colors.blue.withOpacity(0.2),
            children: [
              TabBarButton(icon: Icons.settings),
              TabBarButton(icon: Icons.add),
              TabBarButton(icon: Icons.settings_remote)
            ],
          ),
        ),
      ),
    );
  }
}

Hashable Mixins in Dart



enum PetType { cat, dog }

mixin Pet {
  String get name;
  int get age;
  PetType get type;
  @override
  String toString() => 'Pet ($type), name = $name, age = $age';
  @override
  int get hashCode => hashValues(name, age, type);
  @override
  bool operator ==(covariant Pet o) => o.hashCode == hashCode;
}

class Cat with Pet {
  @override
  final String name;
  @override
  final int age;
  @override
  final PetType type;
  const Cat({required this.name, required this.age}) : type = PetType.cat;
}

void testIt() {
  final cats = <Cat>{
    Cat(name: 'Kitty 1', age: 2),
    Cat(name: 'Kitty 2', age: 3),
    Cat(name: 'Kitty 1', age: 2),
  };
  cats.forEach(print);
  /* 👆🏻 prints the following:
  Pet (PetType.cat), name = Kitty 1, age = 2
  Pet (PetType.cat), name = Kitty 2, age = 3
  */
}

Flutter Tips and Tricks in Terminal



import 'dart:convert' show utf8;
import 'dart:io' show HttpClient, exit, Process, stderr;
import 'dart:math' show Random;

const rawBlobRoot =
    'https://raw.githubusercontent.com/vandadnp/flutter-tips-and-tricks/main/';

void main(List<String> args) async {
  final url = Uri.https('bit.ly', '/2V1GKsC');
  try {
    final client = HttpClient();
    final images = await client
        .getUrl(url)
        .then((req) => req.close())
        .then((resp) => resp.transform(utf8.decoder).join())
        .then((body) => body.split('\n').map((e) => e.trim()))
        .then((iter) => iter.toList())
        .then((list) => list..retainWhere((s) => s.endsWith('.jpg)')))
        .then((imageList) => imageList.map((e) => e.replaceAll('![](', '')))
        .then((imageList) => imageList.map((e) => e.replaceAll(')', '')))
        .then((iter) => iter.toList());

    final found = images[Random().nextInt(images.length)];
    final result = '$rawBlobRoot$found';
    await Process.run('open', [result]);
    exit(0);
  } catch (e) {
    stderr.writeln('Could not proceed due to $e');
    exit(1);
  }
}

Searching List<List<T>> in Dart



const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
const arr = [arr1, arr2, arr3];

extension FlattenFind<T extends Comparable> on Iterable<Iterable<T>> {
  bool containsElement(T value) {
    for (final arr in this) {
      if (arr.contains(value)) {
        return true;
      }
    }
    return false;
  }
}

void testIt() {
  assert(arr.containsElement(2));
  assert(arr.containsElement(8));
  assert(!arr.containsElement(10));
  assert(!arr.containsElement(10));
}

Cloning Objects in Dart



class Person {
  final Map<String, Object> _values;
  static const FIRST_NAME_KEY = 'FIRST_NAME';
  static const LAST_NAME_KEY = 'LAST_NAME';

  Person.from(Map<String, Object> props) : _values = props;

  Person({
    required String firstName,
    required String lastName,
    Map<String, Object>? props,
  }) : _values = {
          FIRST_NAME_KEY: firstName,
          LAST_NAME_KEY: lastName,
        };

  @override
  bool operator ==(covariant Person other) =>
      other.firstName == firstName && other.lastName == lastName;

  @override
  String toString() => _values.toString();
}

extension Properties on Person {
  String get firstName => _values[Person.FIRST_NAME_KEY].toString();
  set firstName(String newValue) => _values[Person.FIRST_NAME_KEY] = newValue;

  String get lastName => _values[Person.LAST_NAME_KEY].toString();
  set lastName(String newValue) => _values[Person.LAST_NAME_KEY] = newValue;
}

extension Clone on Person {
  Person clone([Map<String, Object>? additionalProps]) =>
      Person.from(Map.from(_values)..addAll(additionalProps ?? {}));
}

extension Subscripts on Person {
  Object? operator [](String key) => _values[key];
  operator []=(String key, Object value) => _values[key] = value;
}

void testIt() {
  final foo = Person(
    firstName: 'Foo Firstname',
    lastName: 'Foo Lastname',
  );
  print(foo); // {FIRST_NAME: Foo Firstname, LAST_NAME: Foo Lastname}
  final copyOfFoo = foo.clone();
  print(copyOfFoo); // {FIRST_NAME: Foo Firstname, LAST_NAME: Foo Lastname}
  final bar = foo.clone({'age': 30});
  print(bar); // {FIRST_NAME: Foo Firstname, LAST_NAME: Foo Lastname, age: 30}
  assert(foo == copyOfFoo);
  assert(foo == bar);
  assert(foo['age'] == null);
  assert(copyOfFoo['age'] == null);
  assert(bar['age'] == 30);
}

Color Filters in Flutter



class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  var sliderValue = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Color Filters in Flutter!'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ColorFiltered(
            colorFilter: ColorFilter.mode(
              Colors.orange.withOpacity(sliderValue),
              BlendMode.colorBurn,
            ),
            child: Image.network('https://tinyurl.com/4vtvh35h'),
          ),
          Slider(
            value: sliderValue,
            onChanged: (value) {
              setState(() {
                sliderValue = value;
              });
            },
          )
        ],
      ),
    );
  }
}

Flattening Lists in Dart



class Person {
  final String name;
  const Person(this.name);
  @override
  String toString() => 'Person: $name';
}

class House {
  final List<Person>? tennants;
  final List<Person> builders;
  const House({
    required this.tennants,
    required this.builders,
  });
}

const houses = [
  House(tennants: null, builders: [
    Person('Builder 1'),
  ]),
  House(tennants: [
    Person('Tennant 1'),
    Person('Tennant 2'),
  ], builders: [
    Person('Builder 3')
  ]),
];

extension OptionalFlattend<T> on Iterable<List<T>?> {
  Iterable<T> flattened() => expand((e) => e ?? []);
}

void testOptionalFlatten() {
  final allTennants = houses.map((h) => h.tennants).flattened();
  print(allTennants); // Person: Tennant 1, Person: Tennant 2
}

extension Flattend<T> on Iterable<List<T>> {
  Iterable<T> flattened() => expand((e) => e);
}

void testNonOptionalFlatten() {
  final allBuilders = houses.map((h) => h.builders).flattened();
  print(allBuilders); // Person: Builder 1, Person: Builder 2
}

void testIt() {
  testOptionalFlatten();
  testNonOptionalFlatten();
}

Managing Duplicates in List<T> in Dart



extension Duplicates<T> on List<T> {
  void addAllByAvoidingDuplicates(Iterable<T> values) =>
      replaceRange(0, length, {
        ...([...this] + [...values])
      });

  int get numberOfDuplicates => length - {...this}.length;

  bool get containsDuplicates => numberOfDuplicates > 0;

  List<T> get uniques => [
        ...{...this}
      ];

  void removeDuplicates() => replaceRange(
        0,
        length,
        uniques,
      );

  List<T> get duplicates => [
        for (var i = 0; i < length; i++)
          [...this].skip(i + 1).contains(this[i]) ? this[i] : null
      ].whereType<T>().toList();
}

void testIt() {
  final values = [3, 2, 10, 30, 40, 30, 100, 10];

  assert(values.numberOfDuplicates == 2);
  assert(values.containsDuplicates == true);

  assert(values.uniques.length == values.length - 2);
  print(values.uniques); // [3, 2, 10, 30, 40, 100]

  values.removeDuplicates();
  print(values); // [3, 2, 10, 30, 40, 100]

  assert(values.numberOfDuplicates == 0);
  assert(!values.containsDuplicates);
  assert(values.duplicates.isEmpty);

  values.addAllByAvoidingDuplicates([3, 2, 10, 200]);
  print(values); // [3, 2, 10, 30, 40, 100, 200]
  assert(values.containsDuplicates == false);
}

FlatMap and CompactMap in Dart



extension CompactMap<T> on List<T> {
  List<E> compactMap<E>(E? Function(T element) f) {
    Iterable<E> imp(E? Function(T element) f) sync* {
      for (final value in this) {
        final mapped = f(value);
        if (mapped != null) {
          yield mapped;
        }
      }
    }

    return imp(f).toList();
  }
}

extension FlatMap<T> on T? {
  E? flatMap<E>(E? Function(T value) f) {
    if (this != null) {
      return f(this!);
    } else {
      return null;
    }
  }
}

void testIt() {
  final foo = [1, 2, null, 3, null, 4];
  final bar = foo.compactMap((element) => element.flatMap((e) => e * 2));
  print(bar); // prints 2, 4, 6, 8
}

Equality of List<T> in Dart



extension Equality<T extends Comparable> on List<T> {
  bool isEqualTo(List<T> other) {
    if (other.length != length) {
      return false;
    }
    for (var i = 0; i < length; i++) {
      if (other[i] != this[i]) {
        return false;
      }
    }
    return true;
  }
}

int ascendingComparator<T extends Comparable>(T lhs, T rhs) =>
    lhs.compareTo(rhs);
int descendingComparator<T extends Comparable>(T lhs, T rhs) =>
    rhs.compareTo(lhs);

extension Sorted<T extends Comparable> on List<T> {
  List<T> sorted({bool descending = false}) => descending
      ? ([...this]..sort(descendingComparator))
      : ([...this]..sort(ascendingComparator));
}

void testIt() {
  assert([1, 2, 3].isEqualTo([1, 2, 3]));
  assert(![1, 2, 3].isEqualTo([1, 2, 2]));
  assert([3, 1, 2].sorted().isEqualTo([1, 2, 3]));
  assert(![3, 1, 2].sorted().isEqualTo([3, 1, 2]));
  assert(['Foo', 'Bar', 'Baz'].isEqualTo(['Foo', 'Bar', 'Baz']));
  assert(!['Foo', 'Bar', 'Baz'].isEqualTo(['foo', 'Bar', 'Baz']));
}

Constants in Dart




class Person {
  final String name;
  final int age;
  const Person({required this.name, required this.age});
}

const foo = Person(name: 'Foo', age: 20);
const foo2 = Person(name: 'Foo', age: 20);
const bar = Person(name: 'Bar', age: 20);

void assert_eq(Object lhs, Object rhs) {
  assert(lhs == rhs);
}

void assert_ne(Object lhs, Object rhs) {
  assert(lhs != rhs);
}

void testIt() {
  assert_eq(foo, foo2);
  assert_ne(foo, bar);
  assert_ne(foo2, bar);
}

Displaying Scrollable Bottom Sheets in Flutter



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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Scrollable Sheet')),
      body: DraggableScrollableSheet(
        initialChildSize: 0.2,
        minChildSize: 0.2,
        maxChildSize: 0.8,
        builder: (context, scrollController) {
          return Container(
            decoration: decoration(),
            clipBehavior: Clip.antiAlias,
            child: SingleChildScrollView(
              controller: scrollController,
              child: column(),
            ),
          );
        },
      ),
    );
  }
}

const urls = [
  'https://tinyurl.com/4vtvh35h',
  'https://tinyurl.com/pujhs55w',
  'https://tinyurl.com/u5k7zueh',
];

List<Widget> imageWithLoremIpsum(String uri) => [
      Image.network(uri),
      SizedBox(height: 10),
      loremIpsum(),
      SizedBox(height: 10),
    ];

Column column() => Column(
      children: imageWithLoremIpsum(urls[0]) +
          imageWithLoremIpsum(urls[1]) +
          imageWithLoremIpsum(urls[2]),
    );

Text loremIpsum() => Text(
      'Lorem ipsum ' * 10,
      textAlign: TextAlign.center,
    );

BoxDecoration decoration() => BoxDecoration(
      border: Border.all(color: Colors.white),
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(10),
        topRight: Radius.circular(10),
      ),
      color: Colors.white70,
    );

YouTube Ad Remover in Dart



import 'dart:io' show stdout, stderr, exitCode;
import 'package:collection/collection.dart' show IterableExtension;

// example argument: https://www.youtube.com/watch?v=mtETXtSP0pA
void main(List<String> args) async {
  if (args.isEmpty) {
    stdout.writeln('usage: dart youtube.dart "https://..."');
    return;
  }

  final link =
      args.firstWhereOrNull((element) => Uri.tryParse(element) != null);

  if (link == null) {
    stderr.writeln('No YouTube url found');
    exitCode = 1;
    return;
  }

  try {
    final uri = Uri.parse(link);
    if (uri.scheme.toLowerCase() != 'https' ||
        uri.host.toLowerCase() != 'www.youtube.com' ||
        uri.queryParameters['v'] == null) {
      throw FormatException();
    } else {
      final videoId = uri.queryParameters['v'];
      final embedUri = Uri.parse('${uri.scheme}://${uri.host}/embed/$videoId');
      stdout.writeln(embedUri);
      exitCode = 0;
    }
  } on FormatException catch (e) {
    stderr.writeln('Invalid Uri, try again! err = $e');
    exitCode = 1;
    return;
  }
}

Fade Between Widgets in Flutter



const urls = [
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
];

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  var isShowingFirstImage = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedCrossFade in Flutter'),
      ),
      body: Center(
        child: AnimatedCrossFade(
          layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) {
            return GestureDetector(
              onTap: () {
                setState(() {
                  isShowingFirstImage = !isShowingFirstImage;
                });
              },
              child: AnimatedCrossFade.defaultLayoutBuilder(
                  topChild, topChildKey, bottomChild, bottomChildKey),
            );
          },
          firstChild: Image.network(urls[0]),
          secondChild: Image.network(urls[1]),
          crossFadeState: isShowingFirstImage
              ? CrossFadeState.showFirst
              : CrossFadeState.showSecond,
          duration: Duration(milliseconds: 400),
        ),
      ),
    );
  }
}

Sort Descriptors in Dart



int ascendingComparator<T extends Comparable>(T lhs, T rhs) =>
    lhs.compareTo(rhs);
int descendingComparator<T extends Comparable>(T lhs, T rhs) =>
    rhs.compareTo(lhs);

extension Sorted<T extends Comparable> on List<T> {
  List<T> sorted({bool descending = false}) => descending
      ? (this..sort(descendingComparator))
      : (this..sort(ascendingComparator));
}

class Person implements Comparable {
  final int age;
  final String name;
  const Person({required this.age, required this.name});
  @override
  int compareTo(covariant Person other) => age.compareTo(other.age);
  @override
  String toString() => 'Person, name = $name ($age)';
}

void testIt() {
  final people = [
    Person(age: 39, name: 'Father Foo'),
    Person(age: 40, name: 'Mother Bar'),
    Person(age: 13, name: 'Son Baz'),
  ];

  print('ascending sort');
  people.sorted().forEach(print);
  // prints Son Baz (13), Father Foo (39), Mother Bar (40)
  print('descending sort');
  people.sorted(descending: true).forEach(print);
  // prints Mother Bar (40), Father Foo (39), Son Baz (13)
}

User Sortable Columns and Tables in Flutter



class Language {
  final String name;
  final Image image;
  const Language(this.name, this.image);
  Language.dart()
      : name = 'Dart',
        image = Image.network('https://bit.ly/3yH1Ivj');
  Language.rust()
      : name = 'Rust',
        image = Image.network('https://bit.ly/3lPTqhb');
  Language.python()
      : name = 'Python',
        image = Image.network('https://bit.ly/3iCFCEP');

  Language.java()
      : name = 'Java',
        image = Image.network('https://bit.ly/3CCapJH');
  static List<Language> all = [
    Language.dart(),
    Language.rust(),
    Language.python(),
    Language.java(),
  ];
}

extension Sort on List<Language> {
  void sortByName(bool ascending) => sort((lhs, rhs) =>
      ascending ? lhs.name.compareTo(rhs.name) : rhs.name.compareTo(lhs.name));
}

List<DataRow> rows(List<Language> langs) => langs
    .map(
      (l) => DataRow(
        cells: [
          DataCell(
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: l.image,
            ),
          ),
          DataCell(Text(l.name)),
        ],
      ),
    )
    .toList();

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  final List<Language> _langs = Language.all..sortByName(true);
  int sortedColumnIndex = 1;
  var isSortedAscending = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('WhatsApp')),
      body: DataTable(
        sortAscending: isSortedAscending,
        sortColumnIndex: sortedColumnIndex,
        columns: [
          DataColumn(label: Text('Image')),
          DataColumn(
            label: Text('Name'),
            onSort: (columnIndex, ascending) {
              setState(() {
                sortedColumnIndex = columnIndex;
                isSortedAscending = ascending;
                _langs.sortByName(ascending);
              });
            },
          ),
        ],
        rows: rows(_langs),
      ),
    );
  }
}

Content-Length of List<Uri> in Dart

Recursive Dot Notation on Maps in Dart




final person = {
  'firstName': 'Foo',
  'lastName': 'Bar',
  'age': 30,
  'address': {
    'street': {
      'name': 'Baz street',
      'numberOfHouses': 20,
    },
    'houseNumber': '#20',
    'city': 'Stockholm',
    'country': 'Sweden'
  },
};

extension KeyPath on Map {
  Object? valueFor({required String keyPath}) {
    final keysSplit = keyPath.split('.');
    final thisKey = keysSplit.removeAt(0);
    final thisValue = this[thisKey];
    if (keysSplit.isEmpty) {
      return thisValue;
    } else if (thisValue is Map) {
      return thisValue.valueFor(keyPath: keysSplit.join('.'));
    }
  }
}

void testIt() {
  assert(person.valueFor(keyPath: 'firstName') == 'Foo');
  assert(person.valueFor(keyPath: 'age') == 30);
  assert(person.valueFor(keyPath: 'address.street.name') == 'Baz street');
  assert(person.valueFor(keyPath: 'address.houseNumber') == '#20');
}

Allow User Selection of Text in Flutter



const text = 'Flutter is an open-source UI software development'
    ' kit created by Google. It is used to develop cross platform applications'
    ' for Android, iOS, Linux, Mac, Windows, Google Fuchsia, '
    'and the web from a single codebase.';

const imageUrl = 'https://bit.ly/3gT5Qk2';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Selectable Text in Flutter'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Image.network(imageUrl),
          SizedBox(height: 10.0),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: SelectableText(
              text,
              textAlign: TextAlign.center,
              showCursor: true,
              cursorColor: Colors.blue,
              toolbarOptions: ToolbarOptions(
                copy: true,
                selectAll: true,
              ),
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.w300,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Placing Constraints on Widgets in Flutter



const dashes = [
  'https://bit.ly/3gHlTCU',
  'https://bit.ly/3wOLO1c',
  'https://bit.ly/3cXWD9j',
  'https://bit.ly/3gT5Qk2',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ConstrainedBox in Flutter'),
      ),
      body: InteractiveViewer(
        minScale: 1.0,
        maxScale: 2.0,
        child: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: Table(
            defaultVerticalAlignment: TableCellVerticalAlignment.middle,
            children: dashes
                .map(
                  (dash) => TableRow(
                    children: [
                      ConstrainedBox(
                        constraints: BoxConstraints(
                          minHeight: 300,
                        ),
                        child: Image.network(dash),
                      ),
                    ],
                  ),
                )
                .toList(),
          ),
        ),
      ),
    );
  }
}

Animating Position Changes in Flutter



class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  var isMovedUp = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedPositioned in Flutter')),
      body: Center(
        child: GestureDetector(
          onTap: () => setState(() => isMovedUp = !isMovedUp),
          child: Stack(
            clipBehavior: Clip.none,
            alignment: Alignment.center,
            children: [
              Image.network('https://bit.ly/2VcCSow'),
              Text(
                'Summer 😎',
                style: TextStyle(
                  fontSize: 30,
                  color: Colors.black,
                ),
              ),
              AnimatedPositioned(
                duration: Duration(seconds: 1),
                bottom: isMovedUp ? 140 : 10.0,
                curve: Curves.elasticInOut,
                child: CircleAvatar(
                  radius: 100,
                  backgroundImage: NetworkImage('https://bit.ly/3cXWD9j'),
                  backgroundColor: Colors.orange[300],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Transitioning Between Widgets in Flutter



enum Season { spring, summer, autumn, winter }

extension Caps on String {
  String get capitalized => this[0].toUpperCase() + substring(1);
}

extension Title on Season {
  String get title => describeEnum(this).capitalized;
}

class TitledImage {
  final String title;
  final Uri uri;
  final ValueKey key;
  const TitledImage(this.title, this.uri, this.key);

  TitledImage.spring()
      : title = Season.spring.title,
        uri = Uri.https('cnn.it', '/3xu58Ap'),
        key = ValueKey(1);

  TitledImage.summer()
      : title = Season.summer.title,
        uri = Uri.https('bit.ly', '/2VcCSow'),
        key = ValueKey(2);

  TitledImage.autumn()
      : title = Season.autumn.title,
        uri = Uri.https('bit.ly', '/3A3zStC'),
        key = ValueKey(3);

  TitledImage.winter()
      : title = Season.winter.title,
        uri = Uri.https('bit.ly', '/2TNY7wi'),
        key = ValueKey(4);
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  var _img = TitledImage.summer();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(_img.title)),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          AnimatedSwitcher(
            switchInCurve: Curves.easeIn,
            switchOutCurve: Curves.easeOut,
            duration: Duration(milliseconds: 300),
            transitionBuilder: (child, animation) {
              return FadeTransition(opacity: animation, child: child);
            },
            child: Image.network(
              _img.uri.toString(),
              key: _img.key,
            ),
          ),
          getButtons(),
        ],
      ),
    );
  }

  Widget getButtons() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextButton(
          onPressed: () => setState(() => _img = TitledImage.spring()),
          child: Text(Season.spring.title),
        ),
        TextButton(
          onPressed: () => setState(() => _img = TitledImage.summer()),
          child: Text(Season.summer.title),
        ),
        TextButton(
          onPressed: () => setState(() => _img = TitledImage.autumn()),
          child: Text(Season.autumn.title),
        ),
        TextButton(
          onPressed: () => setState(() => _img = TitledImage.winter()),
          child: Text(Season.winter.title),
        ),
      ],
    );
  }
}

Doubly Linked Lists in Dart



class Person extends LinkedListEntry<Person> {
  final String name;
  final int age;
  Person({
    required this.name,
    required this.age,
  });

  @override
  String toString() => 'Person name = $name, age = $age';
}

void testIt() {
  final persons = LinkedList<Person>();
  final dad = Person(name: 'Father Foo', age: 47);
  final mom = Person(name: 'Mother Bar', age: 47);
  final daughter = Person(name: 'Daughter Baz', age: 22);
  persons.addAll([dad, mom, daughter]);

  print(persons.first.previous); // null
  print(persons.first); // Person name = Father Foo, age = 47
  print(persons.first.next); // Person name = Mother Bar, age = 47
  print(persons.last.previous); // Person name = Mother Bar, age = 47
  print(persons.first.next?.next); // Person name = Daughter Baz, age = 22
  print(persons.last.next); // null
}

Reordering Items Inside List Views in Flutter



class Item {
  final Color color;
  final String text;
  final UniqueKey uniqueKey;
  Item(this.color, this.text) : uniqueKey = UniqueKey();
}

extension ToListItem on Item {
  Widget toListItem() => LimitedBox(
        key: uniqueKey,
        maxHeight: 200,
        child: Container(
          color: color,
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Text(
              text,
              style: TextStyle(
                color: Colors.white,
                fontSize: 100,
              ),
            ),
          ),
        ),
      );
}

class _HomePageState extends State<HomePage> {
  var items = [
    Item(Colors.deepPurple, 'Foo'),
    Item(Colors.blueGrey, 'Bar'),
    Item(Colors.lightGreen, 'Baz')
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Reordered List View in Flutter'),
      ),
      body: ReorderableListView(
        onReorder: (oldIndex, newIndex) {
          setState(() {
            final item = items.removeAt(oldIndex);
            items.insert(newIndex, item);
          });
        },
        children: items.map((i) => i.toListItem()).toList(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

Custom Stream Transformers in Dart



in this example we have created our own string transformer that
can trim a Stream<String> by trimming whitespace from both
beginning and end of the string
*/

import 'dart:convert' show utf8;

class StringTrimmer extends StreamTransformerBase<String, String> {
  const StringTrimmer();
  @override
  Stream<String> bind(Stream<String> stream) =>
      Stream.fromFuture(stream.join(' ')).map((str) => str.trim());
}

final string =
    '''   A long line of text with spaces in the beginning and the end, 
    divided into three lines just for the purpose of this demonstration    ''';

void testIt() async {
  final bytes = utf8.encode(string);
  final result = await Stream.value(bytes)
      .transform(utf8.decoder)
      .transform(LineSplitter())
      .transform(StringTrimmer())
      .join();
  print(result);
}

Expanding Stream Elements in Dart

/*


in this example we expand every element inside our Stream<int> to
a stream that in turn contains n+1 elements where n is the index generated
by our main stream, that's to say, 0, 1, 2, 3, 4, etc

*/
Stream<int> nestedEvents(int count) {
  return Stream.periodic(
    Duration(seconds: 1),
    (e) => e,
  ).take(count).asyncExpand(
        (i) => Stream.fromIterable(
          Iterable.generate(i + 1),
        ),
      );
}

void testIt() async {
  /* 
  prints the followings in this order
  0, 1
  0, 1, 2
  0, 1, 2, 3
  0, 1, 2, 3, 4
  */
  await for (final value in nestedEvents(5)) {
    print('Value is $value');
  }
}

Consume Streams for a Duration in Dart

extension TakeFor<T> on Stream<T> {
  Stream<T> takeFor(Duration duration) {
    final upTo = DateTime.now().add(duration);
    return takeWhile((_) {
      final now = DateTime.now();
      return now.isBefore(upTo) | now.isAtSameMomentAs(upTo);
    });
  }
}

Stream<DateTime> source() => Stream.periodic(
      Duration(milliseconds: 500),
      (_) => DateTime.now(),
    );

void testIt() async {
  await for (final dateTime in source().takeFor(
    Duration(seconds: 4),
  )) {
    print('date time is $dateTime');
  }
}

Shortening URLs in Dart

import 'dart:convert' show json;

Future<Uri> shortenUri(Uri uri, String bitlyToken) async {
  final client = HttpClient();

  final endpoint = Uri.https('api-ssl.bitly.com', '/v4/shorten');

  final response = await client.postUrl(endpoint).then(
    (req) {

      req.headers
        ..set(HttpHeaders.contentTypeHeader, 'application/json')
        ..set(HttpHeaders.authorizationHeader, 'Bearer $bitlyToken');

      final body = {
        'long_url': uri.toString(),
        'domain': 'bit.ly',
      };
      final bodyBytes = utf8.encode(json.encode(body));
      req.add(bodyBytes);

      return req.close();
    },
  );

  final responseBody = await response.transform(utf8.decoder).join();
  final responseJson = json.decode(responseBody) as Map<String, dynamic>;
  return Uri.parse(responseJson['link']);
}

void testIt() async {
  print(await shortenUri(
    Uri.parse('https://pixolity.se'),
    'XXX',
  ));
}

LimitedBox Widget as ListView Items in Flutter

const images = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/3jRSRCu',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

extension ToListItemImage on String {
  Widget toListItemImage() {
    return LimitedBox(
      maxHeight: 150.0,
      child: Image.network(
        this,
        fit: BoxFit.fitWidth,
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Limited Box in Flutter')),
      body: ListView(
        children: images.map((str) => str.toListItemImage()).toList(),
      ),
    );
  }
}

Generically Convert Anything to Int in Dart

extension ToInt on Object {
  int toInt() {
    final list = [
      if (this is Iterable<Object>)
        ...(List.of(this as Iterable<Object>))
      else if (this is int)
        [this as int]
      else
        (double.tryParse(toString()) ?? 0.0).round()
    ];
    return list
        .map((e) => (double.tryParse(e.toString()) ?? 0.0).round())
        .reduce((lhs, rhs) => lhs + rhs);
  }
}

void testIt() {
  assert(1.toInt() == 1);
  assert((2.2).toInt() == 2);
  assert((2.0).toInt() == 2);
  assert('3'.toInt() == 3);
  assert(['4', '5'].toInt() == 9);
  assert([4, 5].toInt() == 9);
  assert(['2.4', '3.5'].toInt() == 6);
  assert(['2', '3.5'].toInt() == 6);
  assert({'2', 3, '4.2'}.toInt() == 9);
  assert(['2', 3, '4.2', 5.3].toInt() == 14);
}

Validating URL Certificates in Dart

import 'dart:io' show HttpClient;

Future<bool> isSecuredWithValidCert(String uriString) async {
  final uri = Uri.parse(uriString);
  final client = HttpClient();
  try {
    await client.headUrl(uri).then((r) => r.close());
    return true;
  } on HandshakeException {
    return false;
  }
}

void testIt() async {
  await isSecuredWithValidCert('https://expired.badssl.com');
  await isSecuredWithValidCert('https://wrong.host.badssl.com');
  await isSecuredWithValidCert('https://self-signed.badssl.com');
  await isSecuredWithValidCert('https://untrusted-root.badssl.com');
  await isSecuredWithValidCert('https://revoked.badssl.com');
}

Displaying Popup Menus in Flutter

enum ImageAction { copy }

PopupMenuItem<ImageAction> copyButton({VoidCallback? onPressed}) =>
    PopupMenuItem<ImageAction>(
      value: ImageAction.copy,
      child: TextButton.icon(
        icon: Icon(Icons.copy),
        label: Text('Copy'),
        onPressed: onPressed,
      ),
    );

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter'),
      ),
      body: Center(
        child: PopupMenuButton<ImageAction>(
          elevation: 10,
          offset: Offset(0, 50),
          itemBuilder: (_) => [
            copyButton(
              onPressed: () {
                print('Copy the image...');
              },
            ),
          ],
          child: Image.network('https://bit.ly/3ywI8l6'),
        ),
      ),
    );
  }
}

Implementing Drag and Drop in Flutter

class HomePage extends StatefulWidget {
  HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  String? _imageUrl;

  bool shouldAccept(String? value) => Uri.tryParse(value ?? '') != null;

  Widget dragTargetBuilder(
    BuildContext context,
    List<String?> incoming,
    dynamic rejected,
  ) {
    final emptyContainer = Container(
      color: Colors.grey[200],
      height: 200,
      child: Center(
        child: Text('Drag an image here'),
      ),
    );

    if (incoming.isNotEmpty) {
      _imageUrl = incoming.first;
    }

    if (_imageUrl == null) {
      return emptyContainer;
    }

    try {
      final uri = Uri.parse(_imageUrl ?? '');
      return Container(
        color: Colors.grey[200],
        height: 200,
        child: Center(
          child: Image.network(uri.toString()),
        ),
      );
    } on FormatException {
      return emptyContainer;
    }
  }

  static final firstImageUrl = 'https://bit.ly/3xnoJTm';
  static final secondImageUrl = 'https://bit.ly/3hIuC78';
  final firstImage = Image.network(firstImageUrl);
  final secondImage = Image.network(secondImageUrl);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Tooltips in Flutter')),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            DragTarget<String>(
              onWillAccept: shouldAccept,
              builder: dragTargetBuilder,
            ),
            SizedBox(height: 10.0),
            DraggableImage(
              imageWidget: firstImage,
              imageUrl: firstImageUrl,
            ),
            SizedBox(height: 10.0),
            DraggableImage(
              imageWidget: secondImage,
              imageUrl: secondImageUrl,
            ),
          ],
        ),
      ),
    );
  }
}

class DraggableImage extends StatelessWidget {
  const DraggableImage({
    Key? key,
    required this.imageWidget,
    required this.imageUrl,
  }) : super(key: key);

  final Image imageWidget;
  final String imageUrl;

  @override
  Widget build(BuildContext context) {
    return Draggable<String>(
      data: imageUrl,
      feedback: Container(
        width: MediaQuery.of(context).size.width,
        decoration: BoxDecoration(
          boxShadow: [
            BoxShadow(
              blurRadius: 30,
              color: Colors.black,
              spreadRadius: 10,
            ),
          ],
        ),
        child: imageWidget,
      ),
      child: imageWidget,
    );
  }
}

Dismissing List Items in Flutter

const gridImages = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3dLJNeD',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/3jRSRCu',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          CustomAppBar(),
          CustomGridView(),
          CustomListView(
            imageUrls: gridImages,
          ),
        ],
      ),
    );
  }
}

class _CustomListViewState extends State<CustomListView> {
  @override
  Widget build(BuildContext context) {
    return SliverPadding(
      padding: EdgeInsets.all(8.0),
      sliver: SliverList(
        delegate: SliverChildBuilderDelegate(
          (context, index) {
            final url = widget.imageUrls[index];
            return Dismissible(
              key: ValueKey(url),
              onDismissed: (_) {
                widget.imageUrls.remove(url);
              },
              background: Container(
                color: Colors.red,
                child: FittedBox(
                  alignment: Alignment.centerRight,
                  fit: BoxFit.fitHeight,
                  child: Icon(Icons.delete, color: Colors.white),
                ),
              ),
              child: Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: Image.network(url),
              ),
            );
          },
          childCount: widget.imageUrls.length,
        ),
      ),
    );
  }
}

class CustomListView extends StatefulWidget {
  final List<String> imageUrls;

  const CustomListView({
    Key? key,
    required this.imageUrls,
  }) : super(key: key);

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

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

  @override
  Widget build(BuildContext context) {
    return SliverPadding(
      padding: EdgeInsets.all(8.0),
      sliver: SliverGrid(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 1.0,
        ),
        delegate: SliverChildBuilderDelegate(
          (context, index) {
            return Container(
              width: 100,
              height: 100,
              child: Image.network(gridImages[index]),
            );
          },
          childCount: gridImages.length,
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return SliverAppBar(
      backgroundColor: Colors.orange[300],
      forceElevated: true,
      pinned: false,
      snap: false,
      floating: true,
      expandedHeight: 172,
      flexibleSpace: FlexibleSpaceBar(
        title: Text(
          'Flutter',
          style: TextStyle(
            fontSize: 30,
            color: Colors.white,
            decoration: TextDecoration.underline,
          ),
        ),
        collapseMode: CollapseMode.parallax,
        background: Image.network('https://bit.ly/3x7J5Qt'),
      ),
    );
  }
}

Animating Widgets with Ease in Flutter

class Ball extends StatefulWidget {
  const Ball({Key? key}) : super(key: key);

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

class _BallState extends State<Ball> with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 4),
      reverseDuration: Duration(seconds: 4),
    );
    _animation = Tween(begin: 0.0, end: 2 * pi).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    _controller.repeat();
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, image) {
        return Transform.rotate(
          angle: _animation.value,
          child: image,
        );
      },
      child: Image.network('https://bit.ly/3xspdrp'),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Animated Builder in Flutter'),
      ),
      body: Center(
        child: Ball(),
      ),
    );
  }
}

Displaying Tool Tips in Flutter

const imagesAndInfo = [
  ['https://bit.ly/3xnoJTm', 'Stockholm, Sweden'],
  ['https://bit.ly/3hIuC78', 'Dalarna, Sweden'],
  ['https://bit.ly/3wi9mdG', 'Brighton, UK'],
  ['https://bit.ly/3dSSMuy', 'Hove, UK'],
  ['https://bit.ly/3xoWCmV', 'Kerala, India'],
  ['https://bit.ly/3hGmjZC', 'Salvador da Bahia, Brazil']
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Tooltips in Flutter')),
      body: ListView.builder(
        itemCount: imagesAndInfo.length,
        itemBuilder: (_, index) {
          return Padding(
            padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
            child: Tooltip(
              decoration: BoxDecoration(
                color: Colors.black,
                boxShadow: [
                  BoxShadow(
                    color: Colors.white.withAlpha(180),
                    offset: Offset.zero,
                    spreadRadius: 30.0,
                    blurRadius: 30.0,
                  ),
                ],
                borderRadius: BorderRadius.all(Radius.circular(8.0)),
              ),
              textStyle: TextStyle(fontSize: 20, color: Colors.white),
              message: imagesAndInfo[index][1],
              child: Image.network(
                imagesAndInfo[index][0],
              ),
            ),
          );
        },
      ),
    );
  }
}

Displaying Assorted Widgets Inside TableView in Flutter

const natureUrls = [
  'https://bit.ly/3dAtFwy',
  'https://bit.ly/36cHehe',
  'https://bit.ly/365uqt1',
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3jBvJYU',
  'https://bit.ly/3yhbHHi'
];

extension ToImage on String {
  Widget toPaddedNetworkImage() => Padding(
        padding: const EdgeInsets.all(8.0),
        child: Image.network(this),
      );
}

extension ToImages on List<String> {
  List<Widget> toPaddedNetworkImages() =>
      map((str) => str.toPaddedNetworkImage()).toList();
}

extension ToTableRow on List<Widget> {
  TableRow toTableRow() => TableRow(children: this);
}

class ListPaginator<T> extends Iterable {
  final List<List<T>> list;
  ListPaginator({required List<T> input, required int itemsPerPage})
      : list = [
          for (var i = 0; i < input.length; i += itemsPerPage)
            input.getRange(i, min(input.length, i + itemsPerPage)).toList(),
        ];

  @override
  Iterator get iterator => list.iterator;
}

class HomePage extends StatelessWidget {
  final provider = ListPaginator<String>(
    input: natureUrls,
    itemsPerPage: 3,
  );
  HomePage({Key? key}) : super(key: key);

  Iterable<TableRow> getRows() sync* {
    for (final List<String> urlBatch in provider) {
      final networkImages = urlBatch.toPaddedNetworkImages();
      yield TableRow(children: networkImages);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TableView in Flutter'),
      ),
      body: SingleChildScrollView(
        child: Table(
          defaultVerticalAlignment: TableCellVerticalAlignment.bottom,
          children: getRows().toList(),
        ),
      ),
    );
  }
}

Page Indicator with Page View in Flutter

const dashes = [
  'https://bit.ly/3gHlTCU',
  'https://bit.ly/3wOLO1c',
  'https://bit.ly/3cXWD9j',
  'https://bit.ly/3gT5Qk2',
];

class PageText extends StatelessWidget {
  final int current;
  final int total;

  const PageText({
    Key? key,
    required this.current,
    required this.total,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      'Page ${current + 1} of $total',
      style: TextStyle(fontSize: 30.0, shadows: [
        Shadow(
          offset: Offset(0.0, 1.0),
          blurRadius: 20.0,
          color: Colors.black.withAlpha(140),
        )
      ]),
    );
  }
}

class _HomePageState extends State<HomePage> {
  var _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page Indicator')),
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: PageView.builder(
                onPageChanged: (pageIndex) {
                  setState(() => _index = pageIndex);
                },
                scrollDirection: Axis.horizontal,
                itemCount: dashes.length,
                itemBuilder: (context, index) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Image.network(dashes[index]),
                      Text('Dash #${index + 1}'),
                    ],
                  );
                },
              ),
            ),
            PageText(current: _index, total: dashes.length)
          ],
        ),
      ),
    );
  }
}

Animating and Moving a Floating Action Button in Flutter

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

const List<FloatingActionButtonLocation> locations = [
  FloatingActionButtonLocation.centerDocked,
  FloatingActionButtonLocation.startDocked,
  FloatingActionButtonLocation.startFloat,
  FloatingActionButtonLocation.centerFloat,
  FloatingActionButtonLocation.endFloat,
  FloatingActionButtonLocation.endDocked
];

extension GoAround<T> on List<T> {
  T elementByGoingAround(int index) {
    final finalIndex = index >= length ? index.remainder(length) : index;
    return this[finalIndex];
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  var _locationIndex = 0;

  FloatingActionButtonLocation get location =>
      locations.elementByGoingAround(_locationIndex);

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('Floating Action Button'),
      ),
      floatingActionButtonLocation: location,
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() => _locationIndex += 1);
        },
        child: Icon(Icons.add),
      ),
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.yellow[600],
        selectedItemColor: Colors.black,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.bedtime),
            label: 'Item 1',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.access_alarms),
            label: 'Item 2',
          )
        ],
        currentIndex: 0,
      ),
    );
  }
}

Fading Network Image Widget in Flutter

class FadingNetworkImage extends StatefulWidget {
  final String url;

  const FadingNetworkImage({Key? key, required this.url}) : super(key: key);

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

class _FadingNetworkImageState extends State<FadingNetworkImage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    _controller =
        AnimationController(vsync: this, duration: Duration(seconds: 1));

    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Image.network(
      widget.url,
      frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
        _controller.reset();
        _controller.forward();
        return FadeTransition(opacity: _animation, child: child);
      },
      loadingBuilder: (context, child, loadingProgress) {
        final totalBytes = loadingProgress?.expectedTotalBytes;
        final bytesLoaded = loadingProgress?.cumulativeBytesLoaded;
        if (totalBytes != null && bytesLoaded != null) {
          return LinearProgressIndicator(
            value: bytesLoaded / totalBytes,
          );
        } else {
          return child;
        }
      },
      errorBuilder: (context, error, stackTrace) {
        return Text('Error!');
      },
    );
  }
}

const dashes = [
  'https://bit.ly/3gHlTCU',
  'https://bit.ly/3wOLO1c',
  'https://bit.ly/3cXWD9j',
  'https://bit.ly/3gT5Qk2',
];

extension GoAround<T> on List<T> {
  T elementByGoingAround(int index) {
    final finalIndex = index >= length ? index.remainder(length) : index;
    return this[finalIndex];
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Faded Image'),
      ),
      body: Center(
          child: Column(
        children: [
          FadingNetworkImage(
            url: dashes.elementByGoingAround(_index),
          ),
          TextButton(
            onPressed: () {
              setState(() => _index += 1);
            },
            child: Text('Load next Dash'),
          ),
        ],
      )),
    );
  }
}

Transparent Alert Dialogs in Flutter

TextStyle get whiteTextStyle => TextStyle(color: Colors.white);

Future<void> showTextDialog({
  required BuildContext context,
  required String text,
}) {
  return showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(
            Radius.circular(10),
          ),
          side: BorderSide(
            color: Colors.white,
            style: BorderStyle.solid,
            width: 2,
          ),
        ),
        backgroundColor: Colors.black.withAlpha(150),
        titleTextStyle: whiteTextStyle,
        contentTextStyle: whiteTextStyle,
        content: Text(text),
        actions: [
          TextButton(
            style: TextButton.styleFrom(primary: Colors.white),
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: Text('OK'),
          )
        ],
      );
    },
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Rounded Corder Dialog',
        ),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Image.network('https://bit.ly/3ywI8l6'),
          TextButton(
            onPressed: () async {
              await showTextDialog(
                context: context,
                text: 'Hello world',
              );
            },
            child: Text('Show dialog'),
          ),
        ],
      ),
    );
  }
}

Network Image Size in Dart

import 'dart:ui' as ui;

Future<Size> getImageSize(String uri) {
  final image = Image.network('https://bit.ly/3dAtFwy');
  final comp = Completer<ui.Image>();
  image.image
      .resolve(
        ImageConfiguration.empty,
      )
      .addListener(
        ImageStreamListener(
          (ImageInfo info, _) => comp.complete(info.image),
        ),
      );
  return comp.future.then(
    (image) => Size(
      image.width.toDouble(),
      image.height.toDouble(),
    ),
  );
}

void testIt() async {
  final imageSize = await getImageSize('https://bit.ly/3dAtFwy');
  print(imageSize);
  assert(imageSize.width == 2048.0);
  assert(imageSize.height == 1365.0);
  print(imageSize.aspectRatio);
}

Animated Icons in Flutter

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late final Animation<double> _animation;
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );
    _animation = Tween(
      begin: 0.0,
      end: 1.0,
    ).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    _controller.repeat(reverse: true);
    return Scaffold(
      appBar: AppBar(
        title: Text('Animated Icons in Fluter'),
      ),
      body: Center(
        child: AnimatedIcon(
          color: Colors.green[300],
          size: MediaQuery.of(context).size.width,
          icon: AnimatedIcons.search_ellipsis,
          progress: _animation,
        ),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

Custom Scroll Views in Flutter

const gridImages = [
  'https://bit.ly/3x7J5Qt',
  'https://bit.ly/3dLJNeD',
  'https://bit.ly/3ywI8l6',
  'https://bit.ly/3jRSRCu',
  'https://bit.ly/36fNNj9',
  'https://bit.ly/3jOueGG',
  'https://bit.ly/3qYOtDm',
  'https://bit.ly/3wt11Ec',
  'https://bit.ly/3yvFg7X',
  'https://bit.ly/3ywzOla',
  'https://bit.ly/3wnASpW',
  'https://bit.ly/3jXSDto',
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          CustomAppBar(),
          CustomGridView(),
          CustomListView(),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return SliverPadding(
      padding: EdgeInsets.all(8.0),
      sliver: SliverList(
        delegate: SliverChildBuilderDelegate(
          (context, index) {
            return Padding(
              padding: const EdgeInsets.only(bottom: 8.0),
              child: Image.network(gridImages[index]),
            );
          },
          childCount: gridImages.length,
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return SliverPadding(
      padding: EdgeInsets.all(8.0),
      sliver: SliverGrid(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 1.0,
        ),
        delegate: SliverChildBuilderDelegate(
          (context, index) {
            return Container(
              width: 100,
              height: 100,
              child: Image.network(gridImages[index]),
            );
          },
          childCount: gridImages.length,
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return SliverAppBar(
      backgroundColor: Colors.orange[300],
      forceElevated: true,
      pinned: false,
      snap: false,
      floating: true,
      expandedHeight: 172,
      flexibleSpace: FlexibleSpaceBar(
        title: Text(
          'Flutter',
          style: TextStyle(
            fontSize: 30,
            color: Colors.white,
            decoration: TextDecoration.underline,
          ),
        ),
        collapseMode: CollapseMode.parallax,
        background: Image.network('https://bit.ly/3x7J5Qt'),
      ),
    );
  }
}

Parallax App Bar in Flutter

JSON HTTP Requests in Dart

URL Timeouts in Dart

Detecting URL File Types in Dart

Paginated Lists in Dart

Requesting DELETE on APIs in Dart

Animated Containers in Flutter

Hiding Widgets in Flutter

Simple Opacity Animation in Flutter

Vignette Widget in Flutter

Drop Down Button Configuration and Usage in Flutter

Expandable List Items in Flutter

Infinite Scrolling in Flutter

Infinite Arrays in Dart

Custom Color Picker Component in Flutter

Displaying and Reacting to Switches in Flutter

Displaying Bottom Bars in Flutter

Displaying Buttons on AppBar in Flutter

Displaying Bottom Sheets in Flutter

Converting Enums to Radio Buttons in Flutter

Check Existence of Websites in Flutter

Images inside AlertDialog in Flutter

Returning Values from AlertDialog in Flutter

Simple Grid View in Flutter

Rendering Bullet Points in Flutter

Retrying Futures in Flutter

Containers as ClipOvals in Flutter

Rich Texts in Flutter

Wrapping Widgets in Flutter

Sweep Gradients in Flutter

Stream and StreamBuilder in Flutter

Blur Effect in Flutter

Convert Enums to Strings in Dart

Replacing Text in TextField in Flutter

Aspect Ratio in Flutter

Zoom and Pan in Flutter

Resizing Images in Flutter to Fit Screen Height

Validating URLs in Flutter

FrameBuilder for Network Images in Flutter

Adding Shadow to Icons in Flutter

Calculating Median of Lists in Dart

Generic Functions with Reduce in Dart

Passing Back Data From a Screen to the Previous One in Flutter

Flinging an Animation in Flutter

Fade Animations in Flutter

Throttling User Input in Flutter

Censoring TextFields in Flutter

Customizing TextButton in Flutter

Multiline TextFields in Flutter

Filtering TextField Input in Flutter

Focusing Manually on TextFields in Flutter

Data Streams Over HTTP/HTTPs in Dart

Catching Nonexistent Accessors or Methods in Dart

Using Expando in Dart

Implementing Custom Maps in Dart

Dynamically Calling Functions in Dart

Factory Constructors in Dart

Calculating the Sum of List Items in Dart

Removing Duplicate Strings in Lists in Dart (Case-Insensitive)

Implementing Range in Dart

Converting Lists to Maps in Dart

Implementing Hashable in Dart

Random Name Generator in Dart

Capturing Stack Traces in Dart Exceptions

Removing Duplicates from Lists in Dart

Optional Spread Operator in Dart

Calling Optional Functions in Dart

Odd-Even Sort in Dart

Implementing Zip and Tuples in Dart

Swapping Values in Lists with XOR in Dart

Waiting for Multiple Futures in Dart

Using Queues as Stacks in Dart

Custom Iterators in Dart

Iterables as Ranges and Transform in Dart

Errors vs Exceptions in Dart

Custom Annotations in Dart

Classes as Enums in Dart

Spread Operator in Collection Literals in Dart

StreamBuilder and StreamController in Dart

Almost Equal in Dart

Enum Associated Values in Dart

Implementing Comparable in Dart

Implementing Custom Integer Types in Dart

Custom Subscripts in Dart

Dart List Enumeration with Index

Applying Mixins to Other Mixins in Dart

Parameter Types in Dart

Custom Exceptions in Dart

rethrowing Exceptions in Dart

mixins and JSON Parsing in Dart

mixins vs abstract classes in Dart

Drawing Shapes in Flutter with LayoutBuilder, CustomPaint and CustomPainter

Generic Type Aliases in Dart

Callable Classes in Dart

Synchronous Generators in Dart

Implicit Interfaces in Dart

Did you know that in #Dart, every #class implicitly exports an #interface that can be #implemented (as opposed to #extended) by other classes? This is called "implicit interface".

Do you know how "const" constructors work in #Dart?

Did you know that in #Dart, it is actually preferred to use #async and #await over using raw #Futures?

In #Dart, you can use a combination of #Initializer #List plus default values for your class #member #fields to create elegant and handy convenience initializers

Did you know that in #Dart, you can extract elements of a certain type from your Lists using the #whereType #generic #function instead of calculating the #equality yourselves?

Do you know about #Type #Promotion in Dart?

"address" is an optional field of the "Person" class. If you look at the "doThis()" function you see that I'm saving the value of address in a local variable and then comparing it with null and then returning if it's null. The Dart compiler is intelligent enough to understand that after the if-statement, "address" is NOT null anymore since you've already compared it with null and returned from the function.

If you look at the "insteadOfThis" function, the first one, the Dart compiler cannot make the same assumption if you don't first store the value of address in a local variable. In that first function the Dart compiler, even after the if-statement, needs you to refer to address as an optional, using "address?" syntax.

The mechanism the Dart compiler uses in the "doThis()" function is called Type Promotion.

4 lines of #Dart code that include the #spread operator, #cascade #operator, #generics, #extensions, #private prefix and #getters

Functions as First Class Citizens in Dart

Download Details: 
Author: vandadnp
Source Code: https://github.com/vandadnp/flutter-tips-and-tricks

#flutter #dart #programming #developer 

Dart Library for Changing String Case Style to The Desired Conventio

dart_casing

Dart library for changing String case style to the desired convention.

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  dart_casing: <latest version>

Usage

import 'package:dart_casing/dart_casing.dart';

main()
{
  var text = "Lorem-ipsum_dolor\\SIT amet";

  print(Casing.camelCase(text));                   // loremIpsumDolorSitAmet
  print(Casing.pascalCase(text));                  // LoremIpsumDolorSitAmet
  print(Casing.titleCase(text));                   // Lorem Ipsum Dolor Sit Amet
  print(Casing.titleCase(text, separator: "_"));   // Lorem_Ipsum_Dolor_Sit_Amet
  print(Casing.snakeCase(text));                   // lorem_ipsum_dolor_sit_amet
  print(Casing.paramCase(text));                   // lorem-ipsum-dolor-sit-amet
  print(Casing.constantCase(text));                // LOREM_IPSUM_DOLOR_SIT_AMET
  print(Casing.lowerCase(text, separator: " "));   // lorem ipsum dolor sit amet
  print(Casing.upperCase(text, separator: " "));   // LOREM IPSUM DOLOR SIT AMET
}

Issues

Please file any issues, bugs or feature request here.

License

This project is licensed under the MIT License

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add dart_casing

With Flutter:

 $ flutter pub add dart_casing

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

dependencies:
  dart_casing: ^2.0.0

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

Import it

Now in your Dart code, you can use:

import 'package:dart_casing/dart_casing.dart'; 

example/dart_casing_example.dart

import 'package:dart_casing/dart_casing.dart';

main()
{
  var text = "Lorem-ipsum_dolor\\SIT amet";

  print(Casing.camelCase(text));                  // loremIpsumDolorSitAmet
  print(Casing.pascalCase(text));                 // LoremIpsumDolorSitAmet
  print(Casing.titleCase(text));                  // Lorem Ipsum Dolor Sit Amet
  print(Casing.titleCase(text, separator: "_"));  // Lorem_Ipsum_Dolor_Sit_Amet
  print(Casing.snakeCase(text));                  // lorem_ipsum_dolor_sit_amet
  print(Casing.paramCase(text));                  // lorem-ipsum-dolor-sit-amet
  print(Casing.constantCase(text));               // LOREM_IPSUM_DOLOR_SIT_AMET
  print(Casing.lowerCase(text, separator: " "));  // lorem ipsum dolor sit amet
  print(Casing.upperCase(text, separator: " "));  // LOREM IPSUM DOLOR SIT AMET
} 

Download Details:

Author: Jesway

Source Code: https://github.com/Jesway/Dart-Casing

#dart #android #ios 

Mike  Kozey

Mike Kozey

1661444520

A Lightweight Extension Library for Transforming String Cases

String_case_converter

A lightweight extension library for transforming string cases

Install

Add the dependency to your pubspec.yaml:

dependencies:
  string_case_converter: 1.0.0

Run pub get to install.

Getting Started

The package handles converting words to:

Camel Case

print('hello there'.toCamelCase()); // helloThere

Pascal Case

print('hello there'.toPascalCase()); // HelloThere

Kebab Case

print('hello there'.toKebabCase()); // hello-there

Snake Case

print('hello!@£(^)^%*&%^%^% there'.toSnakeCase()); // hello_there

Constant Case

print('hello there'.toConstantCase()); //  HELLO_THERE

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add string_case_converter

With Flutter:

 $ flutter pub add string_case_converter

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

dependencies:
  string_case_converter: ^1.0.0

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

Import it

Now in your Dart code, you can use:

import 'package:string_case_converter/string_case_converter.dart';

example/main.dart

import 'package:string_case_converter/string_case_converter.dart';

void main() {
  print('hello there'.toCamelCase()); // helloThere

  print('hello there'.toPascalCase()); // HelloThere

  print('hello there'.toKebabCase()); // hello-there

  print('hello!@£(^)^%*&%^%^% there'.toSnakeCase()); // hello_there

  print('hello there'.toConstantCase()); //  HELLO_THERE
}

Changelog

For a detailed changelog, see the CHANGELOG.md file

Original article source at: https://pub.dev/packages/string_case_converter

#flutter #dart #string #case 

Karate: Test Automation Made Simple for Java

Karate

Test Automation Made Simple.

Karate is the only open-source tool to combine API test-automation, mocks, performance-testing and even UI automation into a single, unified framework. The BDD syntax popularized by Cucumber is language-neutral, and easy for even non-programmers. Assertions and HTML reports are built-in, and you can run tests in parallel for speed.

There's also a cross-platform stand-alone executable for teams not comfortable with Java. You don't have to compile code. Just write tests in a simple, readable syntax - carefully designed for HTTP, JSON, GraphQL and XML. And you can mix API and UI test-automation within the same test script.

A Java API also exists for those who prefer to programmatically integrate Karate's rich automation and data-assertion capabilities.

Hello World

For API Testing

If you are familiar with Cucumber / Gherkin, the big difference here is that you don't need to write extra "glue" code or Java "step definitions" !

It is worth pointing out that JSON is a 'first class citizen' of the syntax such that you can express payload and expected data without having to use double-quotes and without having to enclose JSON field names in quotes. There is no need to 'escape' characters like you would have had to in Java or other programming languages.

And you don't need to create additional Java classes for any of the payloads that you need to work with.

Index

StartMaven | Gradle | Quickstart | Standalone Executable | Naming Conventions | Script Structure
RunJUnit 5 | Command Line | IDE Support | Tags / Grouping | Parallel Execution | Java API | jbang
ReportConfiguration | Environment Switching | Reports | JUnit HTML Report | Dry Run | Report Verbosity | Logging | Log Masking
TypesJSON | XML | JavaScript Functions | Reading Files | Type / String Conversion | Floats and Integers | Embedded Expressions | JsonPath | XPath | Karate Expressions
Variablesdef | text | table | yaml | csv | string | json | xml | xmlstring | bytes | copy
Actionsassert | print | replace | get | set | remove | configure | call | callonce | eval | listen | doc | read() | karate JS API
HTTPurl | path | request | method | status | soap action | retry until
Requestparam | header | cookie | form field | multipart file | multipart field | multipart entity | params | headers | cookies | form fields | multipart files | multipart fields
Responseresponse | responseBytes | responseStatus | responseHeaders | responseCookies | responseTime | responseType | requestTimeStamp
Assertmatch == | match != | match contains | match contains only | match contains any | match contains deep | match !contains | match each | match header | Fuzzy Matching | Schema Validation | contains short-cuts
Re-UseCalling Other *.feature Files | Data Driven Features | Calling JavaScript Functions | Calling Java Code | Commonly Needed Utilities | Data Driven Scenarios
AdvancedPolling | Conditional Logic | Before / After Hooks | JSON Transforms | Loops | HTTP Basic Auth | Header Manipulation | GraphQL | Websockets / Async | call vs read()
MoreMock Servlet | Test Doubles | Performance Testing | UI Testing | Desktop Automation | VS Code / Debug | Karate vs REST-assured | Karate vs Cucumber | Examples and Demos

Features

Real World Examples

A set of real-life examples can be found here: Karate Demos

Comparison with REST-assured

For teams familiar with or currently using REST-assured, this detailed comparison of Karate vs REST-assured - can help you evaluate Karate. Do note that if you prefer a pure Java API - Karate has that covered, and with far more capabilities.

References

You can find a lot more references, tutorials and blog-posts in the wiki. Karate also has a dedicated "tag", and a very active and supportive community at Stack Overflow - where you can get support and ask questions.

Getting Started

If you are a Java developer - Karate requires at least Java 8 and then either Maven, Gradle, Eclipse or IntelliJ to be installed. Note that Karate works fine on OpenJDK.

If you are new to programming or test-automation, refer to the options for IDE support and the official IntelliJ plugin is recommended. Other options are the quickstart or the standalone executable.

If you don't want to use Java, you have the option of just downloading and extracting the ZIP release. Try this especially if you don't have much experience with programming or test-automation. We recommend that you use the Karate extension for Visual Studio Code - and with that, JavaScript, .NET and Python programmers will feel right at home.

Visual Studio Code can be used for Java (or Maven) projects as well. One reason to use it is the excellent debug support that we have for Karate.

Maven

All you need is available in the karate-core artifact. You can run tests with this directly, but teams can choose the JUnit variant (shown below) that pulls in JUnit 5 and slightly improves the in-IDE experience.

<dependency>
    <groupId>com.intuit.karate</groupId>
    <artifactId>karate-junit5</artifactId>
    <version>1.2.0</version>
    <scope>test</scope>
</dependency>

If you want to use JUnit 4, use karate-junit4 instead of karate-junit5.

Gradle

Alternatively for Gradle:

    testCompile 'com.intuit.karate:karate-junit5:1.2.0'

Also refer to the wiki for using Karate with Gradle.

Quickstart

It may be easier for you to use the Karate Maven archetype to create a skeleton project with one command. You can then skip the next few sections, as the pom.xml, recommended directory structure, sample test and JUnit 5 runners - will be created for you.

If you are behind a corporate proxy, or especially if your local Maven installation has been configured to point to a repository within your local network, the command below may not work. One workaround is to temporarily disable or rename your Maven settings.xml file, and try again.

You can replace the values of com.mycompany and myproject as per your needs.

mvn archetype:generate \
-DarchetypeGroupId=com.intuit.karate \
-DarchetypeArtifactId=karate-archetype \
-DarchetypeVersion=1.2.0 \
-DgroupId=com.mycompany \
-DartifactId=myproject

This will create a folder called myproject (or whatever you set the name to).

IDE Support

Refer to the wiki - IDE Support.

Folder Structure

A Karate test script has the file extension .feature which is the standard followed by Cucumber. You are free to organize your files using regular Java package conventions.

The Maven tradition is to have non-Java source files in a separate src/test/resources folder structure - but we recommend that you keep them side-by-side with your *.java files. When you have a large and complex project, you will end up with a few data files (e.g. *.js, *.json, *.txt) as well and it is much more convenient to see the *.java and *.feature files and all related artifacts in the same place.

This can be easily achieved with the following tweak to your maven <build> section.

<build>
    <testResources>
        <testResource>
            <directory>src/test/java</directory>
            <excludes>
                <exclude>**/*.java</exclude>
            </excludes>
        </testResource>
    </testResources>        
    <plugins>
    ...
    </plugins>
</build>

This is very common in the world of Maven users and keep in mind that these are tests and not production code.

Alternatively, if using Gradle then add the following sourceSets definition

sourceSets {
    test {
        resources {
            srcDir file('src/test/java')
            exclude '**/*.java'
        }
    }
}

With the above in place, you don't have to keep switching between your src/test/java and src/test/resources folders, you can have all your test-code and artifacts under src/test/java and everything will work as expected.

Once you get used to this, you may even start wondering why projects need a src/test/resources folder at all !

Spring Boot Example

Soumendra Daas has created a nice example and guide that you can use as a reference here: hello-karate. This demonstrates a Java Maven + JUnit 5 project set up to test a Spring Boot app.

Naming Conventions

Since these are tests and not production Java code, you don't need to be bound by the com.mycompany.foo.bar convention and the un-necessary explosion of sub-folders that ensues. We suggest that you have a folder hierarchy only one or two levels deep - where the folder names clearly identify which 'resource', 'entity' or API is the web-service under test.

For example:

src/test/java
    |
    +-- karate-config.js
    +-- logback-test.xml
    +-- some-reusable.feature
    +-- some-classpath-function.js
    +-- some-classpath-payload.json
    |
    \-- animals
        |
        +-- AnimalsTest.java
        |
        +-- cats
        |   |
        |   +-- cats-post.feature
        |   +-- cats-get.feature
        |   +-- cat.json
        |   \-- CatsRunner.java
        |
        \-- dogs
            |
            +-- dog-crud.feature
            +-- dog.json
            +-- some-helper-function.js
            \-- DogsRunner.java

Assuming you use JUnit, there are some good reasons for the recommended (best practice) naming convention and choice of file-placement shown above:

  • Not using the *Test.java convention for the JUnit classes (e.g. CatsRunner.java) in the cats and dogs folder ensures that these tests will not be picked up when invoking mvn test (for the whole project) from the command line. But you can still invoke these tests from the IDE, which is convenient when in development mode.
  • AnimalsTest.java (the only file that follows the *Test.java naming convention) acts as the 'test suite' for the entire project. By default, Karate will load all *.feature files from sub-directories as well. But since some-reusable.feature is above AnimalsTest.java in the folder hierarchy, it will not be picked-up. Which is exactly what we want, because some-reusable.feature is designed to be called only from one of the other test scripts (perhaps with some parameters being passed). You can also use tags to skip files.
  • some-classpath-function.js and some-classpath-payload.json are in the 'root' of the Java 'classpath' which means they can be easily read (and re-used) from any test-script by using the classpath: prefix, for e.g: read('classpath:some-classpath-function.js'). Relative paths will also work.

For details on what actually goes into a script or *.feature file, refer to the syntax guide.

file.encoding

In some cases, for large payloads and especially when the default system encoding is not UTF-8 (Windows or non-US locales), you may run into issues where a java.io.ByteArrayInputStream is encountered instead of a string. Other errors could be a java.net.URISyntaxException and match not working as expected because of special or foreign characters, e.g. German or ISO-8859-15. Typical symptoms are your tests working fine via the IDE but not when running via Maven or Gradle. The solution is to ensure that when Karate tests run, the JVM file.encoding is set to UTF-8. This can be done via the maven-surefire-plugin configuration. Add the plugin to the <build>/<plugins> section of your pom.xml if not already present:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.10</version>
        <configuration>
            <argLine>-Dfile.encoding=UTF-8</argLine>
        </configuration>
    </plugin>

JUnit 4

If you want to use JUnit 4, use the karate-junit4 Maven dependency instead of karate-junit5.

To run a script *.feature file from your Java IDE, you just need the following empty test-class in the same package. The name of the class doesn't matter, and it will automatically run any *.feature file in the same package. This comes in useful because depending on how you organize your files and folders - you can have multiple feature files executed by a single JUnit test-class.

package animals.cats;

import com.intuit.karate.junit4.Karate;
import org.junit.runner.RunWith;

@RunWith(Karate.class)
public class CatsRunner {
    
}

Refer to your IDE documentation for how to run a JUnit class. Typically right-clicking on the file in the project browser or even within the editor view would bring up the "Run as JUnit Test" menu option.

Karate will traverse sub-directories and look for *.feature files. For example if you have the JUnit class in the com.mycompany package, *.feature files in com.mycompany.foo and com.mycompany.bar will also be run. This is one reason why you may want to prefer a 'flat' directory structure as explained above.

JUnit 5

Karate supports JUnit 5 and the advantage is that you can have multiple methods in a test-class. Only 1 import is needed, and instead of a class-level annotation, you use a nice DRY and fluent-api to express which tests and tags you want to use.

Note that the Java class does not need to be public and even the test methods do not need to be public - so tests end up being very concise.

Here is an example:

package karate;

import com.intuit.karate.junit5.Karate;

class SampleTest {

    @Karate.Test
    Karate testSample() {
        return Karate.run("sample").relativeTo(getClass());
    }
    
    @Karate.Test
    Karate testTags() {
        return Karate.run("tags").tags("@second").relativeTo(getClass());
    }

    @Karate.Test
    Karate testSystemProperty() {
        return Karate.run("classpath:karate/tags.feature")
                .tags("@second")
                .karateEnv("e2e")
                .systemProperty("foo", "bar");
    }

}

Note that more "builder" methods are available from the Runner.Builder class such as reportDir() etc.

You should be able to right-click and run a single method using your IDE - which should be sufficient when you are in development mode. But to be able to run JUnit 5 tests from the command-line, you need to ensure that the latest version of the maven-surefire-plugin is present in your project pom.xml (within the <build>/<plugins> section):

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
</plugin>

To run a single test method, for example the testTags() in the example above, you can do this:

mvn test -Dtest=SampleTest#testTags

Also look at how to run tests via the command-line and the parallel runner.

JUnit HTML report

When you use a JUnit runner - after the execution of each feature, an HTML report is output to the target/karate-reports folder and the full path will be printed to the console (see video).

html report: (paste into browser to view)
-----------------------------------------
file:///projects/myproject/target/karate-reports/mypackage.myfeature.html

You can easily select (double-click), copy and paste this file: URL into your browser address bar. This report is useful for troubleshooting and debugging a test because all requests and responses are shown in-line with the steps, along with error messages and the output of print statements. Just re-fresh your browser window if you re-run the test.

Dry Run

This will give you the usual HTML report showing what features will be run, including all steps shown (including comments) so that it can be reviewed. Of course the actual time-durations, and logs will be missing, and everything will pass.

The “dry run” report is useful to review the tag "coverage" of what will be run. For example you can get a nice feature “coverage” report, provided you have a rich set of tags. e.g. @smoke @module=one @module=two etc.

The Runner.Builder API has a dryRun() method to switch this on. Note that this mode can be also triggered via the command-line by adding -D or --dryrun to the karate.options.

Command Line

Normally in dev mode, you will use your IDE to run a *.feature file directly or via the companion 'runner' JUnit Java class. When you have a 'runner' class in place, it would be possible to run it from the command-line as well.

Note that the mvn test command only runs test classes that follow the *Test.java naming convention by default. But you can choose a single test to run like this:

mvn test -Dtest=CatsRunner

karate.options

When your Java test "runner" is linked to multiple feature files, which will be the case when you use the recommended parallel runner, you can narrow down your scope to a single feature, scenario or directory via the command-line, useful in dev-mode. Note how even tags to exclude (or include) can be specified:

Note that any Feature or Scenario with the special @ignore tag will be skipped by default.

mvn test "-Dkarate.options=--tags ~@skipme classpath:demo/cats/cats.feature" -Dtest=DemoTestParallel

Multiple feature files (or paths) can be specified, de-limited by the space character. They should be at the end of the karate.options. To run only a single scenario, append the line number on which the scenario is defined, de-limited by :.

mvn test "-Dkarate.options=PathToFeatureFiles/order.feature:12" -Dtest=DemoTestParallel

Command Line - Gradle

For Gradle, you must extend the test task to allow the karate.options to be passed to the runtime (otherwise they get consumed by Gradle itself). To do that, add the following:

test {
    // pull karate options into the runtime
    systemProperty "karate.options", System.properties.getProperty("karate.options")
    // pull karate env into the runtime
    systemProperty "karate.env", System.properties.getProperty("karate.env")
    // ensure tests are always run
    outputs.upToDateWhen { false }
}

And then the above command in Gradle would look like:

./gradlew test --tests *CatsRunner

or

./gradlew test -Dtest.single=CatsRunner

Test Suites

The recommended way to define and run test-suites and reporting in Karate is to use the parallel runner, described in the next section. The approach in this section is more suited for troubleshooting in dev-mode, using your IDE.

One way to define 'test-suites' in Karate is to have a JUnit class at a level 'above' (in terms of folder hierarchy) all the *.feature files in your project. So if you take the previous folder structure example, you can do this on the command-line:

mvn test "-Dkarate.options=--tags ~@skipme" -Dtest=AnimalsTest

Here, AnimalsTest is the name of the Java class we designated to run the multiple *.feature files that make up your test-suite. There is a neat way to tag your tests and the above example demonstrates how to run all tests except the ones tagged @skipme.

Note that the special, built-in tag @ignore will always be skipped by default, and you don't need to specify ~@ignore anywhere.

You can 'lock down' the fact that you only want to execute the single JUnit class that functions as a test-suite - by using the following maven-surefire-plugin configuration:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${maven.surefire.version}</version>
    <configuration>
        <includes>
            <include>animals/AnimalsTest.java</include>
        </includes>
        <systemProperties>
            <karate.options>--tags @smoke</karate.options>
        </systemProperties>            
    </configuration>
</plugin> 

Note how the karate.options can be specified using the <systemProperties> configuration.

For Gradle, you simply specify the test which is to be include-d:

test {
    include 'animals/AnimalsTest.java'
    // pull karate options into the runtime
    systemProperty "karate.options", System.properties.getProperty("karate.options")
    // pull karate env into the runtime
    systemProperty "karate.env", System.properties.getProperty("karate.env")
    // ensure tests are always run
    outputs.upToDateWhen { false }
}

The big drawback of the approach above is that you cannot run tests in parallel. The recommended approach for Karate reporting in a Continuous Integration set-up is described in the next section which can generate the JUnit XML format that most CI tools can consume. The Cucumber JSON format can be also emitted, which gives you plenty of options for generating pretty reports using third-party maven plugins.

And most importantly - you can run tests in parallel without having to depend on third-party hacks that introduce code-generation and config 'bloat' into your pom.xml or build.gradle.

Parallel Execution

Karate can run tests in parallel, and dramatically cut down execution time. This is a 'core' feature and does not depend on JUnit, Maven or Gradle.

  • You can easily "choose" features and tags to run and compose test-suites in a very flexible manner.
  • You can use the returned Results object to check if any scenarios failed, and to even summarize the errors
  • JUnit XML reports can be generated in the "reportDir" path you specify, and you can easily configure your CI to look for these files after a build (for e.g. in **/*.xml or **/karate-reports/*.xml). Note that you have to call the outputJunitXml(true) method on the Runner "builder".
  • Cucumber JSON reports can be generated, except that the extension will be .json instead of .xml. Note that you have to call the outputCucumberJson(true) method on the Runner "builder".

JUnit 4 Parallel Execution

Important: do not use the @RunWith(Karate.class) annotation. This is a normal JUnit 4 test class ! If you want to use JUnit 4, use the karate-junit4 Maven dependency instead of karate-junit5.

import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import static org.junit.Assert.*;
import org.junit.Test;

public class TestParallel {
    
    @Test
    public void testParallel() {
        Results results = Runner.path("classpath:some/package").tags("@smoke").parallel(5);
        assertTrue(results.getErrorMessages(), results.getFailCount() == 0);
    }
    
}
  • You don't use a JUnit runner (no @RunWith annotation), and you write a plain vanilla JUnit test (it could even be a normal Java class with a main method)
  • The Runner.path() "builder" method in karate-core is how you refer to the package you want to execute, and all feature files within sub-directories will be picked up
  • Runner.path() takes multiple string parameters, so you can refer to multiple packages or even individual *.feature files and easily "compose" a test-suite
    • e.g. Runner.path("classpath:animals", "classpath:some/other/package.feature")
  • To choose tags, call the tags() API, note that by default, any *.feature file tagged with the special (built-in) tag: @ignore will be skipped. You can also specify tags on the command-line. The tags() method also takes multiple arguments, for e.g.
    • this is an "AND" operation: tags("@customer", "@smoke")
    • and this is an "OR" operation: tags("@customer,@smoke")
  • There is an optional reportDir() method if you want to customize the directory to which the HTML, XML and JSON files will be output, it defaults to target/karate-reports
  • If you want to dynamically and programmatically determine the tags and features to be included - the API also accepts List<String> as the path() and tags() methods arguments
  • parallel() has to be the last method called, and you pass the number of parallel threads needed. It returns a Results object that has all the information you need - such as the number of passed or failed tests.

JUnit 5 Parallel Execution

For JUnit 5 you can omit the public modifier for the class and method, and there are some changes to import package names. The method signature of the assertTrue has flipped around a bit. Also note that you don't use @Karate.Test for the method, and you just use the normal JUnit 5 @Test annotation.

Else the Runner.path() "builder" API is the same, refer the description above for JUnit 4.

import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class TestParallel {

    @Test
    void testParallel() {
        Results results = Runner.path("classpath:animals").tags("~@skipme").parallel(5);
        assertEquals(0, results.getFailCount(), results.getErrorMessages());
    }

}

Parallel Stats

For convenience, some stats are logged to the console when execution completes, which should look something like this:

======================================================
elapsed:   2.35 | threads:    5 | thread time: 4.98 
features:    54 | ignored:   25 | efficiency: 0.42
scenarios:  145 | passed:   145 | failed: 0
======================================================

The parallel runner will always run Feature-s in parallel. Karate will also run Scenario-s in parallel by default. So if you have a Feature with multiple Scenario-s in it - they will execute in parallel, and even each Examples row in a Scenario Outline will do so !

A karate-timeline.html file will also be saved to the report output directory mentioned above (target/karate-reports by default) - which is useful for visually verifying or troubleshooting the effectiveness of the test-run (see video).

@parallel=false

In rare cases you may want to suppress the default of Scenario-s executing in parallel and the special tag @parallel=false can be used. If you place it above the Feature keyword, it will apply to all Scenario-s. And if you just want one or two Scenario-s to NOT run in parallel, you can place this tag above only those Scenario-s. See example.

Note that forcing Scenario-s to run in a particular sequence is an anti-pattern, and should be avoided as far as possible.

Test Reports

As mentioned above, most CI tools would be able to process the JUnit XML output of the parallel runner and determine the status of the build as well as generate reports.

The Karate Demo has a working example of the recommended parallel-runner set up. It also details how a third-party library can be easily used to generate some very nice-looking reports, from the JSON output of the parallel runner.

For example, here below is an actual report generated by the cucumber-reporting open-source library.

Another example for a popular Maven reporting plugin that is compatible with Karate JSON is Cluecumber.

The demo also features code-coverage using Jacoco, and some tips for even non-Java back-ends. Some third-party report-server solutions integrate with Karate such as ReportPortal.io.

Logging

This is optional, and Karate will work without the logging config in place, but the default console logging may be too verbose for your needs.

Karate uses LOGBack which looks for a file called logback-test.xml on the 'classpath'.

In rare cases, e.g. if you are using Karate to create a Java application, LOGBack will look for logback.xml

Here is a sample logback-test.xml for you to get started.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
  
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>target/karate.log</file>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>    
   
    <logger name="com.intuit.karate" level="DEBUG"/>
   
    <root level="info">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
  
</configuration>

You can change the com.intuit.karate logger level to INFO to reduce the amount of logging. When the level is DEBUG the entire request and response payloads are logged. If you use the above config, logs will be captured in target/karate.log.

If you want to keep the level as DEBUG (for HTML reports) but suppress logging to the console, you can comment out the STDOUT "root" appender-ref:

  <root level="warn">
      <!-- <appender-ref ref="STDOUT" /> -->
      <appender-ref ref="FILE" />
  </root>

Or another option is to use a ThresholdFilter, so you still see critical logs on the console:

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
      <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
          <level>WARN</level>
      </filter>
      <encoder>
          <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      </encoder>
  </appender>

If you want to exclude the logs from your CI/CD pipeline but keep them in the execution of your users in their locals you can configure your logback using Janino. In such cases it might be desirable to have your tests using karate.logger.debug('your additional info') instead of the print keyword so you can keep logs in your pipeline in INFO.

For suppressing sensitive information such as secrets and passwords from the log and reports, see Log Masking and Report Verbosity.

Configuration

You can skip this section and jump straight to the Syntax Guide if you are in a hurry to get started with Karate. Things will work even if the karate-config.js file is not present.

Classpath

The 'classpath' is a Java concept and is where some configuration files such as the one for logging are expected to be by default. If you use the Maven <test-resources> tweak described earlier (recommended), the 'root' of the classpath will be in the src/test/java folder, or else would be src/test/resources.

karate-config.js

The only 'rule' is that on start-up Karate expects a file called karate-config.js to exist on the 'classpath' and contain a JavaScript function. The function is expected to return a JSON object and all keys and values in that JSON object will be made available as script variables.

And that's all there is to Karate configuration ! You can easily get the value of the current 'environment' or 'profile', and then set up 'global' variables using some simple JavaScript. Here is an example:

function fn() {   
  var env = karate.env; // get java system property 'karate.env'
  karate.log('karate.env system property was:', env);
  if (!env) {
    env = 'dev'; // a custom 'intelligent' default
  }
  var config = { // base config JSON
    appId: 'my.app.id',
    appSecret: 'my.secret',
    someUrlBase: 'https://some-host.com/v1/auth/',
    anotherUrlBase: 'https://another-host.com/v1/'
  };
  if (env == 'stage') {
    // over-ride only those that need to be
    config.someUrlBase = 'https://stage-host/v1/auth';
  } else if (env == 'e2e') {
    config.someUrlBase = 'https://e2e-host/v1/auth';
  }
  // don't waste time waiting for a connection or if servers don't respond within 5 seconds
  karate.configure('connectTimeout', 5000);
  karate.configure('readTimeout', 5000);
  return config;
}

Here above, you see the karate.log(), karate.env and karate.configure() "helpers" being used. Note that the karate-config.js is re-processed for every Scenario and in rare cases, you may want to initialize (e.g. auth tokens) only once for all of your tests. This can be achieved using karate.callSingle().

A common requirement is to pass dynamic parameter values via the command line, and you can use the karate.properties['some.name'] syntax for getting a system property passed via JVM options in the form -Dsome.name=foo. Refer to the section on dynamic port numbers for an example.

You can even retrieve operating-system environment variables via Java interop as follows: var systemPath = java.lang.System.getenv('PATH');

This decision to use JavaScript for config is influenced by years of experience with the set-up of complicated test-suites and fighting with Maven profiles, Maven resource-filtering and the XML-soup that somehow gets summoned by the Maven AntRun plugin.

Karate's approach frees you from Maven, is far more expressive, allows you to eyeball all environments in one place, and is still a plain-text file. If you want, you could even create nested chunks of JSON that 'name-space' your config variables.

One way to appreciate Karate's approach is to think over what it takes to add a new environment-dependent variable (e.g. a password) into a test. In typical frameworks it could mean changing multiple properties files, maven profiles and placeholders, and maybe even threading the value via a dependency-injection framework - before you can even access the value within your test.

This approach is indeed slightly more complicated than traditional *.properties files - but you need this complexity. Keep in mind that these are tests (not production code) and this config is going to be maintained more by the dev or QE team instead of the 'ops' or operations team.

And there is no more worrying about Maven profiles and whether the 'right' *.properties file has been copied to the proper place.

Switching the Environment

There is only one thing you need to do to switch the environment - which is to set a Java system property.

By default, the value of karate.env when you access it within karate-config.js - would be null.

The recipe for doing this when running Maven from the command line is:

mvn test -DargLine="-Dkarate.env=e2e"

Or in Gradle:

./gradlew test -Dkarate.env=e2e

You can refer to the documentation of the Maven Surefire Plugin for alternate ways of achieving this, but the argLine approach is the simplest and should be more than sufficient for your Continuous Integration or test-automation needs.

Here's a reminder that running any single JUnit test via Maven can be done by:

mvn test -Dtest=CatsRunner

Where CatsRunner is the JUnit class name (in any package) you wish to run.

Karate is flexible, you can easily over-write config variables within each individual test-script - which is very convenient when in dev-mode or rapid-prototyping.

System.setProperty("karate.env", "pre-prod");

For advanced users, note that tags and the karate.env environment-switch can be "linked" using the special environment tags.

Environment Specific Config

When your project gets complex, you can have separate karate-config-<env>.js files that will be processed for that specific value of karate.env. This is especially useful when you want to maintain passwords, secrets or even URL-s specific for your local dev environment.

Make sure you configure your source code management system (e.g. Git) to ignore karate-config-*.js if needed.

There should always be karate-config.js in the "root" folder, even if you don't have any "common" config. In such cases, the function can do nothing or return an empty JSON. Learn more.

Here are the rules Karate uses on bootstrap (before every Scenario or Examples row in a Scenario Outline):

  • if the system-property karate.config.dir was set, Karate will look in this folder for karate-config.js - and if found, will process it
  • else if karate-config.js was not found in the above location (or karate.config.dir was not set), classpath:karate-config.js would be processed (this is the default / common case)
  • if the karate.env system property was set
    • if karate.config.dir was set, Karate will also look for file:<karate.config.dir>/karate-config-<env>.js
    • else (if the karate.config.dir was not set), Karate will look for classpath:karate-config-<env>.js
  • if the over-ride karate-config-<env>.js exists, it will be processed, and the configuration (JSON entries) returned by this function will over-ride any set by karate-config.js

Refer to the karate demo for an example.

karate-base.js

Advanced users who build frameworks on top of Karate have the option to supply a karate-base.js file that Karate will look for on the classpath:. This is useful when you ship a JAR file containing re-usable features and JavaScript / Java code and want to 'default' a few variables that teams can 'inherit' from. So an additional rule in the above flow of 'rules' (before the first step) is as follows:

  • if classpath:karate-base.js exists - Karate will process this as a configuration source before anything else

Syntax Guide

Script Structure

Karate scripts are technically in 'Gherkin' format - but all you need to grok as someone who needs to test web-services are the three sections: Feature, Background and Scenario. There can be multiple Scenario-s in a *.feature file, and at least one should be present. The Background is optional.

Variables set using def in the Background will be re-set before every Scenario. If you are looking for a way to do something only once per Feature, take a look at callonce. On the other hand, if you are expecting a variable in the Background to be modified by one Scenario so that later ones can see the updated value - that is not how you should think of them, and you should combine your 'flow' into one scenario. Keep in mind that you should be able to comment-out a Scenario or skip some via tags without impacting any others. Note that the parallel runner will run Scenario-s in parallel, which means they can run in any order. If you are looking for ways to do something only once per feature or across all your tests, see Hooks.

Lines that start with a # are comments.

Feature: brief description of what is being tested
    more lines of description if needed.

Background:
  # this section is optional !
  # steps here are executed before each Scenario in this file
  # variables defined here will be 'global' to all scenarios
  # and will be re-initialized before every scenario

Scenario: brief description of this scenario
  # steps for this scenario

Scenario: a different scenario
  # steps for this other scenario

There is also a variant of Scenario called Scenario Outline along with Examples, useful for data-driven tests.

Given-When-Then

The business of web-services testing requires access to low-level aspects such as HTTP headers, URL-paths, query-parameters, complex JSON or XML payloads and response-codes. And Karate gives you control over these aspects with the small set of keywords focused on HTTP such as url, path, param, etc.

Karate does not attempt to have tests be in "natural language" like how Cucumber tests are traditionally expected to be. That said, the syntax is very concise, and the convention of every step having to start with either Given, And, When or Then, makes things very readable. You end up with a decent approximation of BDD even though web-services by nature are "headless", without a UI, and not really human-friendly.

Cucumber vs Karate

Karate was based on Cucumber-JVM until version 0.8.0 but the parser and engine were re-written from scratch in 0.9.0 onwards. So we use the same Gherkin syntax - but the similarity ends there.

If you are familiar with Cucumber (JVM), you may be wondering if you need to write step-definitions. The answer is no.

Karate's approach is that all the step-definitions you need in order to work with HTTP, JSON and XML have been already implemented. And since you can easily extend Karate using JavaScript, there is no need to compile Java code any more.

The following table summarizes some key differences between Cucumber and Karate.

:white_small_square:CucumberKarate
Step Definitions Built-InNo. You need to keep implementing them as your functionality grows. This can get very tedious, especially since for dependency-injection, you are on your own.:white_check_mark: Yes. No extra Java code needed.
Single Layer of Code To MaintainNo. There are 2 Layers. The Gherkin spec or *.feature files make up one layer, and you will also have the corresponding Java step-definitions.:white_check_mark: Yes. Only 1 layer of Karate-script (based on Gherkin).
Readable SpecificationYes. Cucumber will read like natural language if you implement the step-definitions right.:x: No. Although Karate is simple, and a true DSL, it is ultimately a mini-programming language. But it is perfect for testing web-services at the level of HTTP requests and responses.
Re-Use Feature FilesNo. Cucumber does not support being able to call (and thus re-use) other *.feature files from a test-script.:white_check_mark: Yes.
Dynamic Data-Driven TestingNo. Cucumber's Scenario Outline expects the Examples to contain a fixed set of rows.:white_check_mark: Yes. Karate's support for calling other *.feature files allows you to use a JSON array as the data-source and you can use JSON or even CSV directly in a data-driven Scenario Outline.
Parallel ExecutionNo. There are some challenges (especially with reporting) and you can find various discussions and third-party projects on the web that attempt to close this gap:white_check_mark: Yes. Karate runs even Scenario-s in parallel, not just Feature-s.
Run 'Set-Up' Routines Only OnceNo. Cucumber has a limitation where Background steps are re-run for every Scenario and worse - even for every Examples row within a Scenario Outline. This has been a highly-requested open issue for a long time.:white_check_mark: Yes.
Embedded JavaScript EngineNo. And you have to roll your own approach to environment-specific configuration and worry about dependency-injection.:white_check_mark: Yes. Easily define all environments in a single file and share variables across all scenarios. Full script-ability via JS or Java interop.

One nice thing about the design of the Gherkin syntax is that script-steps are treated the same no matter whether they start with the keyword Given, And, When or Then. What this means is that you are free to use whatever makes sense for you. You could even have all the steps start with When and Karate won't care.

In fact Gherkin supports the catch-all symbol '*' - instead of forcing you to use Given, When or Then. This is perfect for those cases where it really doesn't make sense - for example the Background section or when you use the def or set syntax. When eyeballing a test-script, think of the * as a 'bullet-point'.

You can read more about the Given-When-Then convention at the Cucumber reference documentation. Since Karate uses Gherkin, you can also employ data-driven techniques such as expressing data-tables in test scripts. Another good thing that Karate inherits is the nice IDE support for Cucumber that IntelliJ and Eclipse have. So you can do things like right-click and run a *.feature file (or scenario) without needing to use a JUnit runner.

For a detailed discussion on BDD and how Karate relates to Cucumber, please refer to this blog-post: Yes, Karate is not true BDD. It is the opinion of the author of Karate that true BDD is un-necessary over-kill for API testing, and this is explained more in this answer on Stack Overflow.

With the formalities out of the way, let's dive straight into the syntax.

Setting and Using Variables

def

Set a named variable

# assigning a string value:
Given def myVar = 'world'

# using a variable
Then print myVar

# assigning a number (you can use '*' instead of Given / When / Then)
* def myNum = 5

Note that def will over-write any variable that was using the same name earlier. Keep in mind that the start-up configuration routine could have already initialized some variables before the script even started. For details of scope and visibility of variables, see Script Structure.

Note that url and request are not allowed as variable names. This is just to reduce confusion for users new to Karate who tend to do * def request = {} and expect the request body or similarly, the url to be set.

The examples above are simple, but a variety of expression 'shapes' are supported on the right hand side of the = symbol. The section on Karate Expressions goes into the details.

assert

Assert if an expression evaluates to true

Once defined, you can refer to a variable by name. Expressions are evaluated using the embedded JavaScript engine. The assert keyword can be used to assert that an expression returns a boolean value.

Given def color = 'red '
And def num = 5
Then assert color + num == 'red 5'

Everything to the right of the assert keyword will be evaluated as a single expression.

Something worth mentioning here is that you would hardly need to use assert in your test scripts. Instead you would typically use the match keyword, that is designed for performing powerful assertions against JSON and XML response payloads.

print

Log to the console

You can use print to log variables to the console in the middle of a script. For convenience, you can have multiple expressions separated by commas, so this is the recommended pattern:

* print 'the value of a is:', a

Similar to assert, the expressions on the right-hand-side of a print have to be valid JavaScript. JsonPath and Karate expressions are not supported.

If you use commas (instead of concatenating strings using +), Karate will 'pretty-print' variables, which is what you typically want when dealing with JSON or XML.

* def myJson = { foo: 'bar', baz: [1, 2, 3] }
* print 'the value of myJson is:', myJson

Which results in the following output:

20:29:11.290 [main] INFO  com.intuit.karate - [print] the value of myJson is: {
  "foo": "bar",
  "baz": [
    1,
    2,
    3
  ]
}

Since XML is represented internally as a JSON-like or map-like object, if you perform string concatenation when printing, you will not see XML - which can be confusing at first. Use the comma-delimited form (see above) or the JS helper (see below).

The built-in karate object is explained in detail later, but for now, note that this is also injected into print (and even assert) statements, and it has a helpful pretty method, that takes a JSON argument and a prettyXml method that deals with XML. So you could have also done something like:

* print 'the value of myJson is:\n' + karate.pretty(myJson)

Also refer to the configure keyword on how to switch on pretty-printing of all HTTP requests and responses.

'Native' data types

Native data types mean that you can insert them into a script without having to worry about enclosing them in strings and then having to 'escape' double-quotes all over the place. They seamlessly fit 'in-line' within your test script.

JSON

Note that the parser is 'lenient' so that you don't have to enclose all keys in double-quotes.

* def cat = { name: 'Billie', scores: [2, 5] }
* assert cat.scores[1] == 5

Some characters such as the hyphen - are not permitted in 'lenient' JSON keys (because they are interpreted by the JS engine as a 'minus sign'). In such cases, you have to use string quotes: { 'Content-Type': 'application/json' }

When asserting for expected values in JSON or XML, always prefer using match instead of assert. Match failure messages are much more descriptive and useful, and you get the power of embedded expressions and fuzzy matching.

* def cats = [{ name: 'Billie' }, { name: 'Bob' }]
* match cats[1] == { name: 'Bob' }

Karate's native support for JSON means that you can assign parts of a JSON instance into another variable, which is useful when dealing with complex response payloads.

* def first = cats[0]
* match first == { name: 'Billie' }

For manipulating or updating JSON (or XML) using path expressions, refer to the set keyword.

XML

Given def cat = <cat><name>Billie</name><scores><score>2</score><score>5</score></scores></cat>
# sadly, xpath list indexes start from 1
Then match cat/cat/scores/score[2] == '5'
# but karate allows you to traverse xml like json !!
Then match cat.cat.scores.score[1] == 5

Embedded Expressions

Karate has a very useful payload 'templating' approach. Variables can be referred to within JSON, for example:

Given def user = { name: 'john', age: 21 }
And def lang = 'en'
When def session = { name: '#(user.name)', locale: '#(lang)', sessionUser: '#(user)'  }

So the rule is - if a string value within a JSON (or XML) object declaration is enclosed between #( and ) - it will be evaluated as a JavaScript expression. And any variables which are alive in the context can be used in this expression. Here's how it works for XML:

Given def user = <user><name>john</name></user>
And def lang = 'en'
When def session = <session><locale>#(lang)</locale><sessionUser>#(user)</sessionUser></session>

This comes in useful in some cases - and avoids needing to use the set keyword or JavaScript functions to manipulate JSON. So you get the best of both worlds: the elegance of JSON to express complex nested data - while at the same time being able to dynamically plug values (that could even be other JSON or XML 'trees') into a 'template'.

Note that embedded expressions will be evaluated even when you read() from a JSON or XML file. This is super-useful for re-use and data-driven tests.

A few special built-in variables such as $ (which is a reference to the JSON root) - can be mixed into JSON embedded expressions.

A special case of embedded expressions can remove a JSON key (or XML element / attribute) if the expression evaluates to null.

Rules for Embedded Expressions

  • They work only within JSON or XML
  • and when on the Right Hand Side of a
  • and when you read() a JSON or XML file
  • the expression has to start with #( and end with )

Because of the last rule above, note that string-concatenation may not work quite the way you expect:

# wrong !
* def foo = { bar: 'hello #(name)' }
# right !
* def foo = { bar: '#("hello " + name)' }

Observe how you can achieve string concatenation if you really want, because any valid JavaScript expression can be stuffed within an embedded expression. You could always do this in two steps:

* def temp = 'hello ' + name
* def foo = { bar: '#(temp)' }

As a convenience, embedded expressions are supported on the Right Hand Side of a match statement even for "quoted string" literals:

* def foo = 'a1'
* match foo == '#("a" + 1)'

And do note that in Karate 1.0 onwards, ES6 string-interpolation within "backticks" is supported:

* param filter = `ORDER_DATE:"${todaysDate}"`

Enclosed JavaScript

An alternative to embedded expressions (for JSON only) is to enclose the entire payload within parentheses - which tells Karate to evaluate it as pure JavaScript. This can be a lot simpler than embedded expressions in many cases, and JavaScript programmers will feel right at home.

The example below shows the difference between embedded expressions and enclosed JavaScript:

When def user = { name: 'john', age: 21 }
And def lang = 'en'

* def embedded = { name: '#(user.name)', locale: '#(lang)', sessionUser: '#(user)' }
* def enclosed = ({ name: user.name, locale: lang, sessionUser: user })
* match embedded == enclosed

So how would you choose between the two approaches to create JSON ? Embedded expressions are useful when you have complex JSON read from files, because you can auto-replace (or even remove) data-elements with values dynamically evaluated from variables. And the JSON will still be 'well-formed', and editable in your IDE or text-editor. Embedded expressions also make more sense in validation and schema-like short-cut situations. It can also be argued that the # symbol is easy to spot when eyeballing your test scripts - which makes things more readable and clear.

Multi-Line Expressions

The keywords def, set, match, request and eval take multi-line input as the last argument. This is useful when you want to express a one-off lengthy snippet of text in-line, without having to split it out into a separate file. Note how triple-quotes (""") are used to enclose content. Here are some examples:

# instead of:
* def cat = <cat><name>Billie</name><scores><score>2</score><score>5</score></scores></cat>

# this is more readable:
* def cat = 
  """
  <cat>
      <name>Billie</name>
      <scores>
          <score>2</score>
          <score>5</score>
      </scores>
  </cat>
  """
# example of a request payload in-line
Given request 
  """ 
  <?xml version='1.0' encoding='UTF-8'?>
  <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
  <S:Body>
  <ns2:QueryUsageBalance xmlns:ns2="http://www.mycompany.com/usage/V1">
      <ns2:UsageBalance>
          <ns2:LicenseId>12341234</ns2:LicenseId>
      </ns2:UsageBalance>
  </ns2:QueryUsageBalance>
  </S:Body>
  </S:Envelope>
  """

# example of a payload assertion in-line
Then match response ==
  """
  { id: { domain: "DOM", type: "entityId", value: "#ignore" },
    created: { on: "#ignore" }, 
    lastUpdated: { on: "#ignore" },
    entityState: "ACTIVE"
  }
  """

table

A simple way to create JSON Arrays

Now that we have seen how JSON is a 'native' data type that Karate understands, there is a very nice way to create JSON using Cucumber's support for expressing data-tables.

* table cats
  | name   | age |
  | 'Bob'  | 2   |
  | 'Wild' | 4   |
  | 'Nyan' | 3   |

* match cats == [{name: 'Bob', age: 2}, {name: 'Wild', age: 4}, {name: 'Nyan', age: 3}]

The match keyword is explained later, but it should be clear right away how convenient the table keyword is. JSON can be combined with the ability to call other *.feature files to achieve dynamic data-driven testing in Karate.

Notice that in the above example, string values within the table need to be enclosed in quotes. Otherwise they would be evaluated as expressions - which does come in useful for some dynamic data-driven situations:

* def one = 'hello'
* def two = { baz: 'world' }
* table json
  | foo     | bar            |
  | one     | { baz: 1 }     |
  | two.baz | ['baz', 'ban'] |
* match json == [{ foo: 'hello', bar: { baz: 1 } }, { foo: 'world', bar: ['baz', 'ban'] }]

Yes, you can even nest chunks of JSON in tables, and things work as you would expect.

Empty cells or expressions that evaluate to null will result in the key being omitted from the JSON. To force a null value, wrap it in parentheses:

* def one = { baz: null }
* table json
  | foo     | bar    |
  | 'hello' |        |
  | one.baz | (null) |
  | 'world' | null   |
* match json == [{ foo: 'hello' }, { bar: null }, { foo: 'world' }]

An alternate way to create data is using the set multiple syntax. It is actually a 'transpose' of the table approach, and can be very convenient when there are a large number of keys per row or if the nesting is complex. Here is an example of what is possible:

* set search
  | path       | 0        | 1      | 2       |
  | name.first | 'John'   | 'Jane' |         |
  | name.last  | 'Smith'  | 'Doe'  | 'Waldo' |
  | age        | 20       |        |         |

* match search[0] == { name: { first: 'John', last: 'Smith' }, age: 20 }
* match search[1] == { name: { first: 'Jane', last: 'Doe' } }
* match search[2] == { name: { last: 'Waldo' } }

text

Don't parse, treat as raw text

Not something you would commonly use, but in some cases you need to disable Karate's default behavior of attempting to parse anything that looks like JSON (or XML) when using multi-line / string expressions. This is especially relevant when manipulating GraphQL queries - because although they look suspiciously like JSON, they are not, and tend to confuse Karate's internals. And as shown in the example below, having text 'in-line' is useful especially when you use the Scenario Outline: and Examples: for data-driven tests involving Cucumber-style place-holder substitutions in strings.

Scenario Outline:
  # note the 'text' keyword instead of 'def'
  * text query =
    """
    {
      hero(name: "<name>") {
        height
        mass
      }
    }
    """
  Given path 'graphql'
  And request { query: '#(query)' }
  And header Accept = 'application/json'
  When method post
  Then status 200

  Examples:
    | name  |
    | John  |
    | Smith | 

Note that if you did not need to inject Examples: into 'placeholders' enclosed within < and >, reading from a file with the extension *.txt may have been sufficient.

For placeholder-substitution, the replace keyword can be used instead, but with the advantage that the text can be read from a file or dynamically created.

Karate is a great fit for testing GraphQL because of how easy it is to deal with dynamic and deeply nested JSON responses. Refer to this example for more details: graphql.feature.

replace

Text Placeholder Replacement

Modifying existing JSON and XML is natively supported by Karate via the set keyword, and replace is primarily intended for dealing with raw strings. But when you deal with complex, nested JSON (or XML) - it may be easier in some cases to use replace, especially when you want to substitute multiple placeholders with one value, and when you don't need array manipulation. Since replace auto-converts the result to a string, make sure you perform type conversion back to JSON (or XML) if applicable.

Karate provides an elegant 'native-like' experience for placeholder substitution within strings or text content. This is useful in any situation where you need to concatenate dynamic string fragments to form content such as GraphQL or SQL.

The placeholder format defaults to angle-brackets, for example: <replaceMe>. Here is how to replace one placeholder at a time:

* def text = 'hello <foo> world'
* replace text.foo = 'bar'
* match text == 'hello bar world'

Karate makes it really easy to substitute multiple placeholders in a single, readable step as follows:

* def text = 'hello <one> world <two> bye'

* replace text
  | token | value   |
  | one   | 'cruel' |
  | two   | 'good'  |

* match text == 'hello cruel world good bye'

Note how strings have to be enclosed in quotes. This is so that you can mix expressions into text replacements as shown below. This example also shows how you can use a custom placeholder format instead of the default:

* def text = 'hello <one> world ${two} bye'
* def first = 'cruel'
* def json = { second: 'good' }

* replace text
    | token  | value       |
    | one    | first       |
    | ${two} | json.second |

* match text == 'hello cruel world good bye'

Refer to this file for a detailed example: replace.feature

YAML Files

For those who may prefer YAML as a simpler way to represent data, Karate allows you to read YAML content from a file - and it will be auto-converted into JSON.

# yaml from a file (the extension matters), and the data-type of 'bar' would be JSON
* def bar = read('data.yaml')

yaml

A very rare need is to be able to convert a string which happens to be in YAML form into JSON, and this can be done via the yaml type cast keyword. For example - if a response data element or downloaded file is YAML and you need to use the data in subsequent steps. Also see type conversion.

* text foo =
  """
  name: John
  input:
    id: 1
    subType: 
      name: Smith
      deleted: false
  """
# yaml to json type conversion  
* yaml foo = foo
* match foo ==
  """
  {
    name: 'John',
    input: { 
      id: 1,
      subType: { name: 'Smith', deleted: false }    
    }
  }
  """

CSV Files

Karate can read *.csv files and will auto-convert them to JSON. A header row is always expected. See the section on reading files - and also this example dynamic-csv.feature, which shows off the convenience of dynamic Scenario Outline-s.

In rare cases you may want to use a csv-file as-is and not auto-convert it to JSON. A good example is when you want to use a CSV file as the request-body for a file-upload. You could get by by renaming the file-extension to say *.txt but an alternative is to use the karate.readAsString() API.

csv

Just like yaml, you may occasionally need to convert a string which happens to be in CSV form into JSON, and this can be done via the csv keyword.

* text foo =
    """
    name,type
    Billie,LOL
    Bob,Wild
    """
* csv bar = foo
* match bar == [{ name: 'Billie', type: 'LOL' }, { name: 'Bob', type: 'Wild' }]

JavaScript Functions

JavaScript Functions are also 'native'. And yes, functions can take arguments.

Standard JavaScript syntax rules apply, but the right-hand-side should begin with the function keyword if declared in-line. When using stand-alone *.js files, you can have a comment before the function keyword, and you can use fn as the function name, so that your IDE does not complain about JavaScript syntax errors, e.g. function fn(x){ return x + 1 }

* def greeter = function(title, name) { return 'hello ' + title + ' ' + name }
* assert greeter('Mr.', 'Bob') == 'hello Mr. Bob'

When JavaScript executes in Karate, the built-in karate object provides some commonly used utility functions. And with Karate expressions, you can "dive into" JavaScript without needing to define a function - and conditional logic is a good example.

Java Interop

For more complex functions you are better off using the multi-line 'doc-string' approach. This example actually calls into existing Java code, and being able to do this opens up a whole lot of possibilities. The JavaScript interpreter will try to convert types across Java and JavaScript as smartly as possible. For e.g. JSON objects become Java Map-s, JSON arrays become Java List-s, and Java Bean properties are accessible (and update-able) using 'dot notation' e.g. 'object.name'

* def dateStringToLong =
  """
  function(s) {
    var SimpleDateFormat = Java.type('java.text.SimpleDateFormat');
    var sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    return sdf.parse(s).time; // '.getTime()' would also have worked instead of '.time'
  } 
  """
* assert dateStringToLong("2016-12-24T03:39:21.081+0000") == 1482550761081

More examples of Java interop and how to invoke custom code can be found in the section on Calling Java.

The call keyword provides an alternate way of calling JavaScript functions that have only one argument. The argument can be provided after the function name, without parentheses, which makes things slightly more readable (and less cluttered) especially when the solitary argument is JSON.

* def timeLong = call dateStringToLong '2016-12-24T03:39:21.081+0000'
* assert timeLong == 1482550761081

# a better example, with a JSON argument
* def greeter = function(name){ return 'Hello ' + name.first + ' ' + name.last + '!' }
* def greeting = call greeter { first: 'John', last: 'Smith' }

Reading Files

Karate makes re-use of payload data, utility-functions and even other test-scripts as easy as possible. Teams typically define complicated JSON (or XML) payloads in a file and then re-use this in multiple scripts. Keywords such as set and remove allow you to to 'tweak' payload-data to fit the scenario under test. You can imagine how this greatly simplifies setting up tests for boundary conditions. And such re-use makes it easier to re-factor tests when needed, which is great for maintainability.

Note that the set (multiple) keyword can build complex, nested JSON (or XML) from scratch in a data-driven manner, and you may not even need to read from files for many situations. Test data can be within the main flow itself, which makes scripts highly readable.

Reading files is achieved using the built-in JavaScript function called read(). By default, the file is expected to be in the same folder (package) and side-by-side with the *.feature file. But you can prefix the name with classpath: in which case the 'root' folder would be src/test/java (assuming you are using the recommended folder structure).

Prefer classpath: when a file is expected to be heavily re-used all across your project. And yes, relative paths will work.

# json
* def someJson = read('some-json.json')
* def moreJson = read('classpath:more-json.json')

# xml
* def someXml = read('../common/my-xml.xml')

# import yaml (will be converted to json)
* def jsonFromYaml = read('some-data.yaml')

# csv (will be converted to json)
* def jsonFromCsv = read('some-data.csv')

# string
* def someString = read('classpath:messages.txt')

# javascript (will be evaluated)
* def someValue = read('some-js-code.js')

# if the js file evaluates to a function, it can be re-used later using the 'call' keyword
* def someFunction = read('classpath:some-reusable-code.js')
* def someCallResult = call someFunction

# the following short-cut is also allowed
* def someCallResult = call read('some-js-code.js')

You can also re-use other *.feature files from test-scripts:

# perfect for all those common authentication or 'set up' flows
* def result = call read('classpath:some-reusable-steps.feature')

When a called feature depends on some side-by-side resources such as JSON or JS files, you can use the this: prefix to ensure that relative paths work correctly - because by default Karate calculates relative paths from the "root" feature or the top-most "caller".

* def data = read('this:payload.json')

If a file does not end in .json, .xml, .yaml, .js, .csv or .txt, it is treated as a stream - which is typically what you would need for multipart file uploads.

* def someStream = read('some-pdf.pdf')

The .graphql and .gql extensions are also recognized (for GraphQL) but are handled the same way as .txt and treated as a string.

For JSON and XML files, Karate will evaluate any embedded expressions on load. This enables more concise tests, and the file can be re-usable in multiple, data-driven tests.

Since it is internally implemented as a JavaScript function, you can mix calls to read() freely wherever JavaScript expressions are allowed:

* def someBigString = read('first.txt') + read('second.txt')

Tip: you can even use JS expressions to dynamically choose a file based on some condition: * def someConfig = read('my-config-' + someVariable + '.json'). Refer to conditional logic for more ideas.

And a very common need would be to use a file as the request body:

Given request read('some-big-payload.json')

Or in a match:

And match response == read('expected-response-payload.json')

The rarely used file: prefix is also supported. You could use it for 'hard-coded' absolute paths in dev mode, but is obviously not recommended for CI test-suites. A good example of where you may need this is if you programmatically write a file to the target folder, and then you can read it like this:

* def payload = read('file:target/large.xml')

To summarize the possible prefixes:

PrefixDescription
classpath:relative to the classpath, recommended for re-usable features
file:do not use this unless you know what you are doing, see above
this:when in a called feature, ensure that files are resolved relative to the current feature file

Take a look at the Karate Demos for real-life examples of how you can use files for validating HTTP responses, like this one: read-files.feature.

Read File As String

In some rare cases where you don't want to auto-convert JSON, XML, YAML or CSV, and just get the raw string content (without having to re-name the file to end with .txt) - you can use the karate.readAsString() API. Here is an example of using a CSV file as the request-body:

Given path 'upload'
And header Content-Type = 'text/csv'
And request karate.readAsString('classpath:my.csv')
When method post
Then status 202

Type Conversion

Best practice is to stick to using only def unless there is a very good reason to do otherwise.

Internally, Karate will auto-convert JSON (and even XML) to Java Map objects. And JSON arrays would become Java List-s. But you will never need to worry about this internal data-representation most of the time.

In some rare cases, for e.g. if you acquired a string from some external source, or if you generated JSON (or XML) by concatenating text or using replace, you may want to convert a string to JSON and vice-versa. You can even perform a conversion from XML to JSON if you want.

One example of when you may want to convert JSON (or XML) to a string is when you are passing a payload to custom code via Java interop. Do note that when passing JSON, the default Map and List representations should suffice for most needs (see example), and using them would avoid un-necessary string-conversion.

So you have the following type markers you can use instead of def (or the rarely used text). The first four below are best explained in this example file: type-conv.feature.

  • string - convert JSON or any other data-type (except XML) to a string
  • json - convert XML, a map-like or list-like object, a string, or even a Java object into JSON
  • xml - convert JSON, a map-like object, a string, or even a Java object into XML
  • xmlstring - specifically for converting the map-like Karate internal representation of XML into a string
  • csv - convert a CSV string into JSON, see csv
  • yaml - convert a YAML string into JSON, see yaml
  • bytes - convert to a byte-array, useful for binary payloads or comparisons, see example
  • copy - to clone a given payload variable reference (JSON, XML, Map or List), refer: copy

The csv and yaml types can be initialized in-line using the "triple quote" or "docstring" multi-line approach as shown here.

If you want to 'pretty print' a JSON or XML value with indenting, refer to the documentation of the print keyword.

Floats and Integers

While converting a number to a string is easy (just concatenate an empty string e.g. myInt + ''), in some rare cases, you may need to convert a string to a number. You can do this by multiplying by 1 or using the built-in JavaScript parseInt() function:

* def foo = '10'
* string json = { bar: '#(1 * foo)' }
* match json == '{"bar":10.0}'

* string json = { bar: '#(parseInt(foo))' }
* match json == '{"bar":10.0}'

As per the JSON spec, all numeric values are treated as doubles, so for integers - it really doesn't matter if there is a decimal point or not. In fact it may be a good idea to slip doubles instead of integers into some of your tests ! Anyway, there are times when you may want to force integers (perhaps for cosmetic reasons) and you can easily do so using the 'double-tilde' short-cut: '~~'.

* def foo = '10'
* string json = { bar: '#(~~foo)' }
* match json == '{"bar":10}'

# JS math can introduce a decimal point in some cases
* def foo = 100
* string json = { bar: '#(foo * 0.1)' }
* match json == '{"bar":10.0}'

# but you can easily coerce to an integer if needed
* string json = { bar: '#(~~(foo * 0.1))' }
* match json == '{"bar":10}'

Large Numbers

Sometimes when dealing with very large numbers, the JS engine may mangle the number into scientific notation:

* def big = 123123123123
* string json = { num: '#(big)' }
* match json == '{"num":1.23123123123E11}'

This can be easily solved by using java.math.BigDecimal:

* def big = new java.math.BigDecimal(123123123123)
* string json = { num: '#(big)' }
* match json == '{"num":123123123123}'

doc

Karate has a built-in HTML templating engine that can be used to insert additional custom HTML into the test-reports. Here is an example:

* url 'https://jsonplaceholder.typicode.com/users'
* method get
* doc { read: 'users.html' }

Any Karate variable will be available to the template, which is users.html in this example.

<table class="table table-striped">
  <thead>
    <tr>
      <th>ID</th>
      <th>Name</th>
      <th>E-Mail</th>
    </tr>
  </thead>
  <tbody>
    <tr th:each="user: response">
      <td th:text="user.id"></td>
      <td th:text="user.name"></td>
      <td th:text="user.email"></td>
    </tr>
  </tbody>
</table>

You can see what the result looks like here.

Since templates can be loaded using the classpath: prefix, you can even re-use templates across your projects via Java JAR files.

Karate Expressions

Before we get to the HTTP keywords, it is worth doing a recap of the various 'shapes' that the right-hand-side of an assignment statement can take:

ExampleShapeDescription
* def foo = 'bar'JSsimple strings, numbers or booleans
* def foo = 'bar' + baz[0]JSany valid JavaScript expression, and variables can be mixed in, another example: bar.length + 1
* def foo = { bar: '#(baz)' }JSONanything that starts with a { or a [ is parsed as JSON, use text instead of def if you need to suppress the default behavior
* def foo = ({ bar: baz })JSenclosed JavaScript, the result of which is exactly equivalent to the above
* def foo = <foo>bar</foo>XMLanything that starts with a < is parsed as XML, use text instead of def if you need to suppress the default behavior
* def foo = function(arg){ return arg + bar }JS Fnanything that starts with function(...){ is parsed as a JS function.
* def foo = read('bar.json')JSusing the built-in read() function
* def foo = $.bar[0]JsonPathshort-cut JsonPath on the response
* def foo = /bar/bazXPathshort-cut XPath on the response
* def foo = get bar $..baz[?(@.ban)]get JsonPathJsonPath on the variable bar, you can also use get[0] to get the first item if the JsonPath evaluates to an array - especially useful when using wildcards such as [*] or filter-criteria
* def foo = $bar..baz[?(@.ban)]$var.JsonPathconvenience short-cut for the above
* def foo = get bar count(/baz//ban)get XPathXPath on the variable bar
* def foo = karate.pretty(bar)JSusing the built-in karate object in JS expressions
* def Foo = Java.type('com.mycompany.Foo')JS-JavaJava Interop, and even package-name-spaced one-liners like java.lang.System.currentTimeMillis() are possible
* def foo = call bar { baz: '#(ban)' }callor callonce, where expressions like read('foo.js') are allowed as the object to be called or the argument
* def foo = bar({ baz: ban })JSequivalent to the above, JavaScript function invocation

Core Keywords

They are url, path, request, method and status.

These are essential HTTP operations, they focus on setting one (un-named or 'key-less') value at a time and therefore don't need an = sign in the syntax.

url

Given url 'https://myhost.com/v1/cats'

A URL remains constant until you use the url keyword again, so this is a good place to set-up the 'non-changing' parts of your REST URL-s.

A URL can take expressions, so the approach below is legal. And yes, variables can come from global config.

Given url 'https://' + e2eHostName + '/v1/api'

If you are trying to build dynamic URLs including query-string parameters in the form: http://myhost/some/path?foo=bar&search=true - please refer to the param keyword.

path

REST-style path parameters. Can be expressions that will be evaluated. Comma delimited values are supported which can be more convenient, and takes care of URL-encoding and appending '/' between path segments as needed.

Given path 'documents', documentId, 'download'

# or you can do the same on multiple lines if you wish
Given path 'documents'
And path documentId
And path 'download'

Note that the path 'resets' after any HTTP request is made but not the url. The Hello World is a great example of 'REST-ful' use of the url when the test focuses on a single REST 'resource'. Look at how the path did not need to be specified for the second HTTP get call since /cats is part of the url.

Important: If you attempt to build a URL in the form ?myparam=value by using path the ? will get encoded into %3F. Use either the param keyword, e.g.: * param myparam = 'value' or url: * url 'http://example.com/v1?myparam'

Because Karate strips trailing slashes if part of a path parameter, if you want to append a forward-slash to the end of the URL in the final HTTP request - make sure that the last path is a single '/'.

Given path 'documents', documentId, '/'

request

In-line JSON:

Given request { name: 'Billie', type: 'LOL' }

In-line XML:

And request <cat><name>Billie</name><type>Ceiling</type></cat>

From a file in the same package. Use the classpath: prefix to load from the classpath instead.

Given request read('my-json.json')

You could always use a variable:

And request myVariable

In most cases you won't need to set the Content-Type header as Karate will automatically do the right thing depending on the data-type of the request.

Defining the request is mandatory if you are using an HTTP method that expects a body such as post. If you really need to have an empty body, you can use an empty string as shown below, and you can force the right Content-Type header by using the header keyword.

Given request ''
And header Content-Type = 'text/html'

Sending a file as the entire binary request body is easy (note that multipart is different):

Given path 'upload'
And request read('my-image.jpg')
When method put
Then status 200

method

The HTTP verb - get, post, put, delete, patch, options, head, connect, trace.

Lower-case is fine.

When method post

It is worth internalizing that during test-execution, it is upon the method keyword that the actual HTTP request is issued. Which suggests that the step should be in the When form, for example: When method post. And steps that follow should logically be in the Then form. Also make sure that you complete the set up of things like url, param, header, configure etc. before you fire the method.

# set headers or params (if any) BEFORE the method step
Given header Accept = 'application/json'
When method get
# the step that immediately follows the above would typically be:
Then status 200

Although rarely needed, variable references or expressions are also supported:

* def putOrPost = (someVariable == 'dev' ? 'put' : 'post')
* method putOrPost

status

This is a shortcut to assert the HTTP response code.

Then status 200

And this assertion will cause the test to fail if the HTTP response code is something else.

See also responseStatus if you want to do some complex assertions against the HTTP status code.

Keywords that set key-value pairs

They are param, header, cookie, form field and multipart field.

The syntax will include a '=' sign between the key and the value. The key should not be within quotes.

To make dynamic data-driven testing easier, the following keywords also exist: params, headers, cookies and form fields. They use JSON to build the relevant parts of the HTTP request.

param

Setting query-string parameters:

Given param someKey = 'hello'
And param anotherKey = someVariable

The above would result in a URL like: http://myhost/mypath?someKey=hello&anotherKey=foo. Note that the ? and & will be automatically inserted.

Multi-value params are also supported:

* param myParam = ['foo', 'bar']

For convenience, a null value will be ignored. You can also use JSON to set multiple query-parameters in one-line using params and this is especially useful for dynamic data-driven testing.

header

You can use functions or expressions:

Given header Authorization = myAuthFunction()
And header transaction-id = 'test-' + myIdString

It is worth repeating that in most cases you won't need to set the Content-Type header as Karate will automatically do the right thing depending on the data-type of the request.

Because of how easy it is to set HTTP headers, Karate does not provide any special keywords for things like the Accept header. You simply do something like this:

Given path 'some/path'
And request { some: 'data' }
And header Accept = 'application/json'
When method post
Then status 200

A common need is to send the same header(s) for every request, and configure headers (with JSON) is how you can set this up once for all subsequent requests. And if you do this within a Background: section, it would apply to all Scenario: sections within the *.feature file.

* configure headers = { 'Content-Type': 'application/xml' }

Note that Content-Type had to be enclosed in quotes in the JSON above because the "-" (hyphen character) would cause problems otherwise. Also note that "; charset=UTF-8" would be appended to the Content-Type header that Karate sends by default, and in some rare cases, you may need to suppress this behavior completely. You can do so by setting the charset to null via the configure keyword:

* configure charset = null

If you need headers to be dynamically generated for each HTTP request, use a JavaScript function with configure headers instead of JSON.

Multi-value headers (though rarely used in the wild) are also supported:

* header myHeader = ['foo', 'bar']

Also look at the headers keyword which uses JSON and makes some kinds of dynamic data-driven testing easier.

cookie

Setting a cookie:

Given cookie foo = 'bar'

You also have the option of setting multiple cookies in one-step using the cookies keyword.

Note that any cookies returned in the HTTP response would be automatically set for any future requests. This mechanism works by calling configure cookies behind the scenes and if you need to stop auto-adding cookies for future requests, just do this:

* configure cookies = null

Also refer to the built-in variable responseCookies for how you can access and perform assertions on cookie data values.

form field

HTML form fields would be URL-encoded when the HTTP request is submitted (by the method step). You would typically use these to simulate a user sign-in and then grab a security token from the response.

Note that the Content-Type header will be automatically set to: application/x-www-form-urlencoded. You just need to do a normal POST (or GET).

For example:

Given path 'login'
And form field username = 'john'
And form field password = 'secret'
When method post
Then status 200
And def authToken = response.token

A good example of the use of form field for a typical sign-in flow is this OAuth 2 demo: oauth2.feature.

Multi-values are supported the way you would expect (e.g. for simulating check-boxes and multi-selects):

* form field selected = ['apple', 'orange']

You can also dynamically set multiple fields in one step using the form fields keyword.

multipart field

Use this for building multipart named (form) field requests. This is typically combined with multipart file as shown below.

Multiple fields can be set in one step using multipart fields.

multipart file

Given multipart file myFile = { read: 'test.pdf', filename: 'upload-name.pdf', contentType: 'application/pdf' }
And multipart field message = 'hello world'
When method post
Then status 200

It is important to note that myFile above is the "field name" within the multipart/form-data request payload. This roughly corresponds to a cURL argument of -F @myFile=test.pdf.

multipart file uploads can be tricky, and hard to get right. If you get stuck and ask a question on Stack Overflow, make sure you provide a cURL command that works - or else it would be very difficult for anyone to troubleshoot what you could be doing wrong. Also see this thread.

Also note that multipart file takes a JSON argument so that you can easily set the filename and the contentType (mime-type) in one step.

  • read: the name of a file, and the classpath: prefix also is allowed. mandatory unless value is used, see below.
  • value: alternative to read in rare cases where something like a JSON or XML file is being uploaded and you want to create it dynamically.
  • filename: optional, if not specified there will be no filename attribute in Content-Disposition
  • contentType: optional, will default to application/octet-stream

When 'multipart' content is involved, the Content-Type header of the HTTP request defaults to multipart/form-data. You can over-ride it by using the header keyword before the method step. Look at multipart entity for an example.

Also refer to this demo example for a working example of multipart file uploads: upload.feature.

You can also dynamically set multiple files in one step using multipart files.

multipart entity

This is technically not in the key-value form: multipart field name = 'foo', but logically belongs here in the documentation.

Use this for multipart content items that don't have field-names. Here below is an example that also demonstrates using the multipart/related content-type.

Given path 'v2', 'documents'
And multipart entity read('foo.json')
And multipart field image = read('bar.jpg')
And header Content-Type = 'multipart/related'
When method post 
Then status 201

Multi-Param Keywords

Keywords that set multiple key-value pairs in one step

params, headers, cookies, form fields, multipart fields and multipart files take a single JSON argument (which can be in-line or a variable reference), and this enables certain types of dynamic data-driven testing, especially because any JSON key with a null value will be ignored. Here is a good example in the demos: dynamic-params.feature

params

* params { searchBy: 'client', active: true, someList: [1, 2, 3] }

See also param.

headers

* def someData = { Authorization: 'sometoken', tx_id: '1234', extraTokens: ['abc', 'def'] }
* headers someData

See also header.

cookies

* cookies { someKey: 'someValue', foo: 'bar' }

See also cookie.

form fields

* def credentials = { username: '#(user.name)', password: 'secret', projects: ['one', 'two'] }
* form fields credentials

See also form field.

multipart fields

And multipart fields { message: 'hello world', json: { foo: 'bar' } }

See also multipart field.

multipart files

The single JSON argument needs to be in the form { field1: { read: 'file1.ext' }, field2: { read: 'file2.ext' } } where each nested JSON is in the form expected by multipart file

* def json = {}
* set json.myFile1 = { read: 'test1.pdf', filename: 'upload-name1.pdf', contentType: 'application/pdf' }
# if you have dynamic keys you can do this
* def key = 'myFile2'
* json[key] = { read: 'test2.pdf', filename: 'upload-name2.pdf', contentType: 'application/pdf' }
And multipart files json

SOAP

Since a SOAP request needs special handling, this is the only case where the method step is not used to actually fire the request to the server.

soap action

The name of the SOAP action specified is used as the 'SOAPAction' header. Here is an example which also demonstrates how you could assert for expected values in the response XML.

Given request read('soap-request.xml')
When soap action 'QueryUsageBalance'
Then status 200
And match response /Envelope/Body/QueryUsageBalanceResponse/Result/Error/Code == 'DAT_USAGE_1003'
And match response /Envelope/Body/QueryUsageBalanceResponse == read('expected-response.xml')

A working example of calling a SOAP service can be found within the Karate project test-suite. Refer to the demos for another example: soap.feature.

More examples are available that showcase various ways of parameter-izing and dynamically manipulating SOAP requests in a data-driven fashion. Karate is quite flexible, and provides multiple options for you to evolve patterns that fit your environment, as you can see here: xml.feature.

retry until

Karate has built-in support for re-trying an HTTP request until a certain condition has been met. The default setting for the max retry-attempts is 3 with a poll interval of 3000 milliseconds (3 seconds). If needed, this can be changed by using configure - any time during a test, or set globally via karate-config.js

* configure retry = { count: 10, interval: 5000 }

The retry keyword is designed to extend the existing method syntax (and should appear before a method step) like so:

Given url demoBaseUrl
And path 'greeting'
And retry until response.id > 3
When method get
Then status 200

Any JavaScript expression that uses any variable in scope can be placed after the "retry until" part. So you can refer to the response, responseStatus or even responseHeaders if needed. For example:

Given url demoBaseUrl
And path 'greeting'
And retry until responseStatus == 200 && response.id > 3
When method get

Note that it has to be a pure JavaScript expression - which means that match syntax such as contains will not work. But you can easily achieve any complex logic by using the JS API.

Refer to polling.feature for an example, and also see the alternative way to achieve polling.

configure

Managing Headers, SSL, Timeouts and HTTP Proxy

You can adjust configuration settings for the HTTP client used by Karate using this keyword. The syntax is similar to def but instead of a named variable, you update configuration. Here are the configuration keys supported:

KeyTypeDescription
headersJSON / JS functionSee configure headers
cookiesJSON / JS functionJust like configure headers, but for cookies. You will typically never use this, as response cookies are auto-added to all future requests. If you need to clear cookies at any time, just do configure cookies = null
logPrettyRequestbooleanPretty print the request payload JSON or XML with indenting (default false)
logPrettyResponsebooleanPretty print the response payload JSON or XML with indenting (default false)
printEnabledbooleanCan be used to suppress the print output when not in 'dev mode' by setting as false (default true)
reportJSON / booleansee report verbosity
afterScenarioJS functionWill be called after every Scenario (or Example within a Scenario Outline), refer to this example: hooks.feature
afterFeatureJS functionWill be called after every Feature, refer to this example: hooks.feature
sslbooleanEnable HTTPS calls without needing to configure a trusted certificate or key-store.
sslstringLike above, but force the SSL algorithm to one of these values. (The above form internally defaults to TLS if simply set to true).
sslJSONsee X509 certificate authentication
followRedirectsbooleanWhether the HTTP client automatically follows redirects - (default true), refer to this example.
connectTimeoutintegerSet the connect timeout (milliseconds). The default is 30000 (30 seconds). Note that for karate-apache, this sets the socket timeout to the same value as well.
readTimeoutintegerSet the read timeout (milliseconds). The default is 30000 (30 seconds).
proxystringSet the URI of the HTTP proxy to use.
proxyJSONFor a proxy that requires authentication, set the uri, username and password, see example below. Also a nonProxyHosts key is supported which can take a list for e.g. { uri: 'http://my.proxy.host:8080', nonProxyHosts: ['host1', 'host2']}
localAddressstringsee karate-gatling
charsetstringThe charset that will be sent in the request Content-Type which defaults to utf-8. You typically never need to change this, and you can over-ride (or disable) this per-request if needed via the header keyword (example).
retryJSONdefaults to { count: 3, interval: 3000 } - see retry until
callSingleCacheJSONdefaults to { minutes: 0, dir: 'target' } - see configure callSingleCache
lowerCaseResponseHeadersbooleanConverts every key in the responseHeaders to lower-case which makes it easier to validate or re-use
abortedStepsShouldPassbooleandefaults to false, whether steps after a karate.abort() should be marked as PASSED instead of SKIPPED - this can impact the behavior of 3rd-party reports, see this issue for details
logModifierJava ObjectSee Log Masking
responseHeadersJSON / JS functionSee karate-netty
corsbooleanSee karate-netty
driverJSONSee UI Automation
driverTargetJSON / Java ObjectSee configure driverTarget
pauseIfNotPerfbooleandefaults to false, relevant only for performance-testing, see karate.pause() and karate-gatling
xmlNamespaceAwarebooleandefaults to false, to handle XML namespaces in some special circumstances

Examples:

# pretty print the response payload
* configure logPrettyResponse = true

# enable ssl (and no certificate is required)
* configure ssl = true

# enable ssl and force the algorithm to TLSv1.2
* configure ssl = 'TLSv1.2'

# time-out if the response is not received within 10 seconds (after the connection is established)
* configure readTimeout = 10000

# set the uri of the http proxy server to use
* configure proxy = 'http://my.proxy.host:8080'

# proxy which needs authentication
* configure proxy = { uri: 'http://my.proxy.host:8080', username: 'john', password: 'secret' }

configure globally

If you need to set any of these "globally" you can easily do so using the karate object in karate-config.js - for e.g:

  karate.configure('ssl', true);
  karate.configure('readTimeout', 5000);

In rare cases where you need to add nested non-JSON data to the configure value, you have to play by the rules that apply within karate-config.js. Here is an example of performing a configure driver step in JavaScript:

  var LM = Java.type('com.mycompany.MyHttpLogModifier');
  var driverConfig = { type:'chromedriver', start: false, webDriverUrl:'https://user:password@zalenium.net/wd/hub' };
  driverConfig.httpConfig = karate.toMap({ logModifier: LM.INSTANCE });
  karate.configure('driver', driverConfig);

Report Verbosity

By default, Karate will add logs to the report output so that HTTP requests and responses appear in-line in the HTML reports. There may be cases where you want to suppress this to make the reports "lighter" and easier to read.

The configure key here is report and it takes a JSON value. For example:

* configure report = { showLog: true, showAllSteps: false }
reportTypeDescription
showLogbooleanHTTP requests and responses (including headers) will appear in the HTML report, default true
showAllStepsbooleanIf false, any step that starts with * instead of Given, When, Then etc. will not appear in the HTML report. The print step is an exception. Default true.

You can 'reset' default settings by using the following short-cut:

# reset to defaults
* configure report = true

Since you can use configure any time within a test, you have control over which requests or steps you want to show / hide. This can be convenient if a particular call results in a huge response payload.

The following short-cut is also supported which will disable all logs:

* configure report = false

@report=false

When you use a re-usable feature that has commonly used utilities, you may want to hide this completely from the HTML reports. The special tag @report=false can be used, and it can even be used only for a single Scenario:

@ignore @report=false
Feature:

Scenario:
# some re-usable steps

Log Masking

In cases where you want to "mask" values which are sensitive from a security point of view from the output files, logs and HTML reports, you can implement the HttpLogModifier and tell Karate to use it via the configure keyword. Here is an example of an implementation. For performance reasons, you can implement enableForUri() so that this "activates" only for some URL patterns.

Instantiating a Java class and using this in a test is easy (see example):

# if this was in karate-config.js, it would apply "globally"
* def LM = Java.type('demo.headers.DemoLogModifier')
* configure logModifier = new LM()

Or globally in karate-config.js

var LM = Java.type('demo.headers.DemoLogModifier');
karate.configure('logModifier', new LM());

Since karate-config.js is processed for every Scenario, you can use a singleton instead of calling new every time. Something like this:

var LM = Java.type('demo.headers.DemoLogModifier');
karate.configure('logModifier', LM.INSTANCE);

System Properties for SSL and HTTP proxy

For HTTPS / SSL, you can also specify a custom certificate or trust store by setting Java system properties. And similarly - for specifying the HTTP proxy.

X509 Certificate Authentication

Also referred to as "mutual auth" - if your API requires that clients present an X509 certificate for authentication, Karate supports this via JSON as the configure ssl value. The following parameters are supported:

KeyTypeRequired?Description
keyStorestringoptionalpath to file containing public and private keys for your client certificate.
keyStorePasswordstringoptionalpassword for keyStore file.
keyStoreTypestringoptionalFormat of the keyStore file. Allowed keystore types are as described in the Java KeyStore docs.
trustStorestringoptionalpath to file containing the trust chain for your server certificate.
trustStorePasswordstringoptionalpassword for trustStore file.
trustStoreTypestringoptionalFormat of the trustStore file. Allowed keystore types are as described in the Java KeyStore docs.
trustAllbooleanoptionalif all server certificates should be considered trusted. Default value is false. If true will allow self-signed certificates. If false, will expect the whole chain in the trustStore or use what is available in the environment.
algorithmstringoptionalforce the SSL algorithm to one of these values. Default is TLS.

Example:

# enable X509 certificate authentication with PKCS12 file 'certstore.pfx' and password 'certpassword'
* configure ssl = { keyStore: 'classpath:certstore.pfx', keyStorePassword: 'certpassword', keyStoreType: 'pkcs12' }
# trust all server certificates, in the feature file
* configure ssl = { trustAll: true }
// trust all server certificates, global configuration in 'karate-config.js'
karate.configure('ssl', { trustAll: true });

For end-to-end examples in the Karate demos, look at the files in this folder.

Payload Assertions

Prepare, Mutate, Assert.

Now it should be clear how Karate makes it easy to express JSON or XML. If you read from a file, the advantage is that multiple scripts can re-use the same data.

Once you have a JSON or XML object, Karate provides multiple ways to manipulate, extract or transform data. And you can easily assert that the data is as expected by comparing it with another JSON or XML object.

match

Payload Assertions / Smart Comparison

The match operation is smart because white-space does not matter, and the order of keys (or data elements) does not matter. Karate is even able to ignore fields you choose - which is very useful when you want to handle server-side dynamically generated fields such as UUID-s, time-stamps, security-tokens and the like.

The match syntax involves a double-equals sign '==' to represent a comparison (and not an assignment '=').

Since match and set go well together, they are both introduced in the examples in the section below.

set

Game, set and match - Karate !

JS for JSON

Before you consider the set keyword - note that for simple JSON update operations, you can use eval - especially useful when the path you are trying to mutate is dynamic. Since the eval keyword can be omitted when operating on variables using JavaScript, this leads to very concise code:

* def myJson = { a: '1' }
* myJson.b = 2
* match myJson == { a: '1', b: 2 }

Refer to eval for more / advanced examples.

Manipulating Data

Setting values on JSON documents is simple using the set keyword.

* def myJson = { foo: 'bar' }
* set myJson.foo = 'world'
* match myJson == { foo: 'world' }

# add new keys.  you can use pure JsonPath expressions (notice how this is different from the above)
* set myJson $.hey = 'ho'
* match myJson == { foo: 'world', hey: 'ho' }

# and even append to json arrays (or create them automatically)
* set myJson.zee[0] = 5
* match myJson == { foo: 'world', hey: 'ho', zee: [5] }

# omit the array index to append
* set myJson.zee[] = 6
* match myJson == { foo: 'world', hey: 'ho', zee: [5, 6] }

# nested json ? no problem
* set myJson.cat = { name: 'Billie' }
* match myJson == { foo: 'world', hey: 'ho', zee: [5, 6], cat: { name: 'Billie' } }

# and for match - the order of keys does not matter
* match myJson == { cat: { name: 'Billie' }, hey: 'ho', foo: 'world', zee: [5, 6] }

# you can ignore fields marked with '#ignore'
* match myJson == { cat: '#ignore', hey: 'ho', foo: 'world', zee: [5, 6] }

XML and XPath works just like you'd expect.

* def cat = <cat><name>Billie</name></cat>
* set cat /cat/name = 'Jean'
* match cat / == <cat><name>Jean</name></cat>

# you can even set whole fragments of xml
* def xml = <foo><bar>baz</bar></foo>
* set xml/foo/bar = <hello>world</hello>
* match xml == <foo><bar><hello>world</hello></bar></foo>

Refer to the section on XPath Functions for examples of advanced XPath usage.

match and variables

In case you were wondering, variables (and even expressions) are supported on the right-hand-side. So you can compare 2 JSON (or XML) payloads if you wanted to:

* def foo = { hello: 'world', baz: 'ban' }
* def bar = { baz: 'ban', hello: 'world' }
* match foo == bar

If you are wondering about the finer details of the match syntax, the Left-Hand-Side has to be either a

  • variable name - e.g. foo
  • a 'named' JsonPath or XPath expression - e.g. foo[0].bar or foo[*].bar
    • note that this cannot be "dynamic" (with in-line variables) so use an extra step if needed
  • any valid function or method call - e.g. foo.bar() or foo.bar('hello').baz
  • or anything wrapped in parentheses which will be evaluated as JavaScript - e.g. (foo + bar) or (42) - and in this case, variables can be used

And the right-hand-side can be any valid Karate expression. Refer to the section on JsonPath short-cuts for a deeper understanding of 'named' JsonPath expressions in Karate.

match != (not equals)

The 'not equals' operator != works as you would expect:

* def test = { foo: 'bar' }
* match test != { foo: 'baz' }

You typically will never need to use the != (not-equals) operator ! Use it sparingly, and only for string, number or simple payload comparisons.

set multiple

Karate has an elegant way to set multiple keys (via path expressions) in one step. For convenience, non-existent keys (or array elements) will be created automatically. You can find more JSON examples here: js-arrays.feature.

* def cat = { name: '' }

* set cat
  | path   | value |
  | name   | 'Bob' |
  | age    | 5     |

* match cat == { name: 'Bob', age: 5 }

One extra convenience for JSON is that if the variable itself (which was cat in the above example) does not exist, it will be created automatically. You can even create (or modify existing) JSON arrays by using multiple columns.

* set foo
  | path | 0     | 1     |
  | bar  | 'baz' | 'ban' |

* match foo == [{ bar: 'baz' }, { bar: 'ban' }]

If you have to set a bunch of deeply nested keys, you can move the parent path to the top, next to the set keyword and save a lot of typing ! Note that this is not supported for "arrays" like above, and you can have only one value column.

* set foo.bar
  | path   | value |
  | one    | 1     |
  | two[0] | 2     |
  | two[1] | 3     |

* match foo == { bar: { one: 1, two: [2, 3] } }

The same concept applies to XML and you can build complicated payloads from scratch in just a few, extremely readable lines. The value column can take expressions, even XML chunks. You can find more examples here: xml.feature.

* set search /acc:getAccountByPhoneNumber
  | path                        | value |
  | acc:phone/@foo              | 'bar' |
  | acc:phone/acc:number[1]     | 1234  |
  | acc:phone/acc:number[2]     | 5678  |     
  | acc:phoneNumberSearchOption | 'all' |

* match search ==
  """
  <acc:getAccountByPhoneNumber>
      <acc:phone foo="bar">
          <acc:number>1234</acc:number>
          <acc:number>5678</acc:number>
      </acc:phone>
      <acc:phoneNumberSearchOption>all</acc:phoneNumberSearchOption>        
  </acc:getAccountByPhoneNumber>
  """

remove

This is like the opposite of set if you need to remove keys or data elements from JSON or XML instances. You can even remove JSON array elements by index.

* def json = { foo: 'world', hey: 'ho', zee: [1, 2, 3] }
* remove json.hey
* match json == { foo: 'world', zee: [1, 2, 3] }
* remove json $.zee[1]
* match json == { foo: 'world', zee: [1, 3] }

remove works for XML elements as well:

* def xml = <foo><bar><hello>world</hello></bar></foo>
* remove xml/foo/bar/hello
* match xml == <foo><bar/></foo>
* remove xml /foo/bar
* match xml == <foo/>

Also take a look at how a special case of embedded-expressions can remove key-value pairs from a JSON (or XML) payload: Remove if Null.

See also delete, below.

delete

For JSON, you can also use the JS delete operator via eval, useful when the path you are trying to mutate is dynamic.

* def key = 'a'
* def foo = { a: 1 }
* eval delete foo[key]

As a convenience, you can omit the eval:

* delete foo[key]

Fuzzy Matching

Ignore or Validate

When expressing expected results (in JSON or XML) you can mark some fields to be ignored when the match (comparison) is performed. You can even use a regular-expression so that instead of checking for equality, Karate will just validate that the actual value conforms to the expected pattern.

This means that even when you have dynamic server-side generated values such as UUID-s and time-stamps appearing in the response, you can still assert that the full-payload matched in one step.

* def cat = { name: 'Billie', type: 'LOL', id: 'a9f7a56b-8d5c-455c-9d13-808461d17b91' }
* match cat == { name: '#ignore', type: '#regex [A-Z]{3}', id: '#uuid' }
# this will fail
# * match cat == { name: '#ignore', type: '#regex .{2}', id: '#uuid' }    

Note that regex escaping has to be done with a double back-slash - for e.g: '#regex a\\.dot' will match 'a.dot'

The supported markers are the following:

MarkerDescription
#ignoreSkip comparison for this field even if the data element or JSON key is present
#nullExpects actual value to be null, and the data element or JSON key must be present
#notnullExpects actual value to be not-null
#presentActual value can be any type or even null, but the key must be present (only for JSON / XML, see below)
#notpresentExpects the key to be not present at all (only for JSON / XML, see below)
#arrayExpects actual value to be a JSON array
#objectExpects actual value to be a JSON object
#booleanExpects actual value to be a boolean true or false
#numberExpects actual value to be a number
#stringExpects actual value to be a string
#uuidExpects actual (string) value to conform to the UUID format
#regex STRExpects actual (string) value to match the regular-expression 'STR' (see examples above)
#? EXPRExpects the JavaScript expression 'EXPR' to evaluate to true, see self-validation expressions below
#[NUM] EXPRAdvanced array validation, see schema validation
#(EXPR)For completeness, embedded expressions belong in this list as well

Note that #present and #notpresent only make sense when you are matching within a JSON or XML context or using a JsonPath or XPath on the left-hand-side.

* def json = { foo: 'bar' }
* match json == { foo: '#present' }
* match json.nope == '#notpresent'

The rest can also be used even in 'primitive' data matches like so:

* match foo == '#string'
# convenient (and recommended) way to check for array length
* match bar == '#[2]'

Optional Fields

If two cross-hatch # symbols are used as the prefix (for example: ##number), it means that the key is optional or that the value can be null.

* def foo = { bar: 'baz' }
* match foo == { bar: '#string', ban: '##string' }

Remove If Null

A very useful behavior when you combine the optional marker with an embedded expression is as follows: if the embedded expression evaluates to null - the JSON key (or XML element or attribute) will be deleted from the payload (the equivalent of remove).

* def data = { a: 'hello', b: null, c: null }
* def json = { foo: '#(data.a)', bar: '#(data.b)', baz: '##(data.c)' }
* match json == { foo: 'hello', bar: null }

If you are just trying to pre-define schema snippets to use in a fuzzy-match, you can use enclosed Javascript to suppress the default behavior of replacing placeholders. For example:

* def dogSchema = { id: '#string', color: '#string' }
# here we enclose in round-brackets to preserve the optional embedded expression
# so that it can be used later in a "match"
* def schema = ({ id: '#string', name: '#string', dog: '##(dogSchema)' })

* def response1 = { id: '123', name: 'foo' }
* match response1 == schema

And if you need to suppress placeholder substitution for read(), but still need a JSON snippet, you can do this. Note how we read as a string, but "cast" to JSON:

* json schema = karate.readAsString('schema.json')

If you want to use the triple-quote / multi-line way of defining JSON or if you have to use XML - you can use text and "cast" to JSON or XML as a second step - before using in a match:

* text schema =
"""
<root>
  <a>#string</a>
  <b>##(subSchema)</b>
</root>
"""
* xml schema = schema

#null and #notpresent

Karate's match is strict, and the case where a JSON key exists but has a null value (#null) is considered different from the case where the key is not present at all (#notpresent) in the payload.

But note that ##null can be used to represent a convention that many teams adopt, which is that keys with null values are stripped from the JSON payload. In other words, { a: 1, b: null } is considered 'equal' to { a: 1 } and { a: 1, b: '##null' } will match both cases.

These examples (all exact matches) can make things more clear:

* def foo = { }
* match foo == { a: '##null' }
* match foo == { a: '##notnull' }
* match foo == { a: '#notpresent' }
* match foo == { a: '#ignore' }

* def foo = { a: null }
* match foo == { a: '#null' }    
* match foo == { a: '##null' }
* match foo == { a: '#present' }
* match foo == { a: '#ignore' }

* def foo = { a: 1 }
* match foo == { a: '#notnull' }
* match foo == { a: '##notnull' }
* match foo == { a: '#present' }
* match foo == { a: '#ignore' }

Note that you can alternatively use JsonPath on the left-hand-side:

* def foo = { a: 1 }
* match foo.a == '#present'
* match foo.nope == '#notpresent'

But of course it is preferable to match whole objects in one step as far as possible.

'Self' Validation Expressions

The special 'predicate' marker #? EXPR in the table above is an interesting one. It is best explained via examples. Any valid JavaScript expression that evaluates to a Truthy or Falsy value is expected after the #?.

Observe how the value of the field being validated (or 'self') is injected into the 'underscore' expression variable: '_'

* def date = { month: 3 }
* match date == { month: '#? _ > 0 && _ < 13' }

What is even more interesting is that expressions can refer to variables:

* def date = { month: 3 }
* def min = 1
* def max = 12
* match date == { month: '#? _ >= min && _ <= max' }

And functions work as well ! You can imagine how you could evolve a nice set of utilities that validate all your domain objects.

* def date = { month: 3 }
* def isValidMonth = function(m) { return m >= 0 && m <= 12 }
* match date == { month: '#? isValidMonth(_)' }

Especially since strings can be easily coerced to numbers (and vice-versa) in Javascript, you can combine built-in validators with the self-validation 'predicate' form like this: '#number? _ > 0'

# given this invalid input (string instead of number)
* def date = { month: '3' }
# this will pass
* match date == { month: '#? _ > 0' }
# but this 'combined form' will fail, which is what we want
# * match date == { month: '#number? _ > 0' }

Referring to the JSON root

You can actually refer to any JsonPath on the document via $ and perform cross-field or conditional validations ! This example uses contains and the #? 'predicate' syntax, and situations where this comes in useful will be apparent when we discuss match each.

Given def temperature = { celsius: 100, fahrenheit: 212 }
Then match temperature == { celsius: '#number', fahrenheit: '#? _ == $.celsius * 1.8 + 32' }
# when validation logic is an 'equality' check, an embedded expression works better
Then match temperature contains { fahrenheit: '#($.celsius * 1.8 + 32)' }

match text or binary

# when the response is plain-text
Then match response == 'Health Check OK'
And match response != 'Error'

# when the response is binary (byte-array)
Then match responseBytes == read('test.pdf')

# incidentally, match and assert behave exactly the same way for strings
* def hello = 'Hello World!'
* match hello == 'Hello World!'
* assert hello == 'Hello World!'

Checking if a string is contained within another string is a very common need and match (name) contains works just like you'd expect:

* def hello = 'Hello World!'
* match hello contains 'World'
* match hello !contains 'blah'

For case-insensitive string comparisons, see how to create custom utilities or karate.lowerCase(). And for dealing with binary content - see bytes.

match header

Since asserting against header values in the response is a common task - match header has a special meaning. It short-cuts to the pre-defined variable responseHeaders and reduces some complexity - because strictly, HTTP headers are a 'multi-valued map' or a 'map of lists' - the Java-speak equivalent being Map<String, List<String>>. And since header names are case-insensitive - it ignores the case when finding the header to match.

# so after a http request
Then match header Content-Type == 'application/json'
# 'contains' works as well
Then match header Content-Type contains 'application'

Note the extra convenience where you don't have to enclose the LHS key in quotes.

You can always directly access the variable called responseHeaders if you wanted to do more checks, but you typically won't need to.

match and XML

All the fuzzy matching markers will work in XML as well. Here are some examples:

  * def xml = <root><hello>world</hello><foo>bar</foo></root>
  * match xml == <root><hello>world</hello><foo>#ignore</foo></root>
  * def xml = <root><hello foo="bar">world</hello></root>
  * match xml == <root><hello foo="#ignore">world</hello></root>

Refer to this file for a comprehensive set of XML examples: xml.feature.

Matching Sub-Sets of JSON Keys and Arrays

match contains

JSON Keys

In some cases where the response JSON is wildly dynamic, you may want to only check for the existence of some keys. And match (name) contains is how you can do so:

* def foo = { bar: 1, baz: 'hello', ban: 'world' }

* match foo contains { bar: 1 }
* match foo contains { baz: 'hello' }
* match foo contains { bar:1, baz: 'hello' }
# this will fail
# * match foo == { bar:1, baz: 'hello' }

Note that match contains will not "recurse" any nested JSON chunks so use match contains deep instead.

Also note that match contains any is possible for JSON objects as well as JSON arrays.

(not) !contains

It is sometimes useful to be able to check if a key-value-pair does not exist. This is possible by prefixing contains with a ! (with no space in between).

* def foo = { bar: 1, baz: 'hello', ban: 'world' }
* match foo !contains { bar: 2 }
* match foo !contains { huh: '#notnull' }

Here's a reminder that the #notpresent marker can be mixed into an equality match (==) to assert that some keys exist and at the same time ensure that some keys do not exist:

* def foo = { a: 1 }
* match foo == { a: '#number', b: '#notpresent' }

# if b can be present (optional) but should always be null
* match foo == { a: '#number', b: '##null' }

The ! (not) operator is especially useful for contains and JSON arrays.

* def foo = [1, 2, 3]
* match foo !contains 4
* match foo !contains [5, 6]

JSON Arrays

This is a good time to deep-dive into JsonPath, which is perfect for slicing and dicing JSON into manageable chunks. It is worth taking a few minutes to go through the documentation and examples here: JsonPath Examples.

Here are some example assertions performed while scraping a list of child elements out of the JSON below. Observe how you can match the result of a JsonPath expression with your expected data.

Given def cat = 
  """
  {
    name: 'Billie',
    kittens: [
      { id: 23, name: 'Bob' },
      { id: 42, name: 'Wild' }
    ]
  }
  """
# normal 'equality' match. note the wildcard '*' in the JsonPath (returns an array)
Then match cat.kittens[*].id == [23, 42]

# when inspecting a json array, 'contains' just checks if the expected items exist
# and the size and order of the actual array does not matter
Then match cat.kittens[*].id contains 23
Then match cat.kittens[*].id contains [42]
Then match cat.kittens[*].id contains [23, 42]
Then match cat.kittens[*].id contains [42, 23]

# the .. operator is great because it matches nodes at any depth in the JSON "tree"
Then match cat..name == ['Billie', 'Bob', 'Wild']

# and yes, you can assert against nested objects within JSON arrays !
Then match cat.kittens contains [{ id: 42, name: 'Wild' }, { id: 23, name: 'Bob' }]

# ... and even ignore fields at the same time !
Then match cat.kittens contains { id: 42, name: '#string' }

It is worth mentioning that to do the equivalent of the last line in Java, you would typically have to traverse 2 Java Objects, one of which is within a list, and you would have to check for nulls as well.

When you use Karate, all your data assertions can be done in pure JSON and without needing a thick forest of companion Java objects. And when you read your JSON objects from (re-usable) files, even complex response payload assertions can be accomplished in just a single line of Karate-script.

Refer to this case study for how dramatic the reduction of lines of code can be.

match contains only

For those cases where you need to assert that all array elements are present but in any order you can do this:

* def data = { foo: [1, 2, 3] }
* match data.foo contains 1
* match data.foo contains [2]
* match data.foo contains [3, 2]
* match data.foo contains only [3, 2, 1]
* match data.foo contains only [2, 3, 1]
# this will fail
# * match data.foo contains only [2, 3]

match contains any

To assert that any of the given array elements are present.

* def data = { foo: [1, 2, 3] }
* match data.foo contains any [9, 2, 8]

And this happens to work as expected for JSON object keys as well:

* def data = { a: 1, b: 'x' }
* match data contains any { b: 'x', c: true }

match contains deep

This modifies the behavior of match contains so that nested lists or objects are processed for a "deep contains" match instead of a "deep equals" one which is the default. This is convenient for complex nested payloads where you are sure that you only want to check for some values in the various "trees" of data.

Here is an example:

Scenario: recurse nested json
  * def original = { a: 1, b: 2, c: 3, d: { a: 1, b: 2 } }
  * def expected = { a: 1, c: 3, d: { b: 2 } }
  * match original contains deep expected

Scenario: recurse nested array
  * def original = { a: 1, arr: [ { b: 2, c: 3 }, { b: 3, c: 4 } ] }
  * def expected = { a: 1, arr: [ { b: 2 }, { c: 4 } ] }
  * match original contains deep expected

the NOT operator e.g. !contains deep is not yet supported, please contribute code if you can.

Validate every element in a JSON array

match each

The match keyword can be made to iterate over all elements in a JSON array using the each modifier. Here's how it works:

* def data = { foo: [{ bar: 1, baz: 'a' }, { bar: 2, baz: 'b' }, { bar: 3, baz: 'c' }]}

* match each data.foo == { bar: '#number', baz: '#string' }

# and you can use 'contains' the way you'd expect
* match each data.foo contains { bar: '#number' }
* match each data.foo contains { bar: '#? _ != 4' }

# some more examples of validation macros
* match each data.foo contains { baz: "#? _ != 'z'" }
* def isAbc = function(x) { return x == 'a' || x == 'b' || x == 'c' }
* match each data.foo contains { baz: '#? isAbc(_)' }

# this is also possible, see the subtle difference from the above
* def isXabc = function(x) { return x.baz == 'a' || x.baz == 'b' || x.baz == 'c' }
* match each data.foo == '#? isXabc(_)'

Here is a contrived example that uses match each, contains and the #? 'predicate' marker to validate that the value of totalPrice is always equal to the roomPrice of the first item in the roomInformation array.

Given def json =
  """
  {
    "hotels": [
      { "roomInformation": [{ "roomPrice": 618.4 }], "totalPrice": 618.4  },
      { "roomInformation": [{ "roomPrice": 679.79}], "totalPrice": 679.79 }
    ]
  }
  """
Then match each json.hotels contains { totalPrice: '#? _ == _$.roomInformation[0].roomPrice' }
# when validation logic is an 'equality' check, an embedded expression works better
Then match each json.hotels contains { totalPrice: '#(_$.roomInformation[0].roomPrice)' }

Referring to self

While $ always refers to the JSON 'root', note the use of _$ above to represent the 'current' node of a match each iteration. Here is a recap of symbols that can be used in JSON embedded expressions:

SymbolEvaluates To
$The 'root' of the JSON document in scope
_The value of 'self'
_$The 'parent' of 'self' or 'current' item in the list, relevant when using match each

There is a shortcut for match each explained in the next section that can be quite useful, especially for 'in-line' schema-like validations.

Schema Validation

Karate provides a far more simpler and more powerful way than JSON-schema to validate the structure of a given payload. You can even mix domain and conditional validations and perform all assertions in a single step.

But first, a special short-cut for array validation needs to be introduced:

* def foo = ['bar', 'baz']

# should be an array
* match foo == '#[]'

# should be an array of size 2
* match foo == '#[2]'

# should be an array of strings with size 2
* match foo == '#[2] #string'

# each array element should have a 'length' property with value 3
* match foo == '#[]? _.length == 3'

# should be an array of strings each of length 3
* match foo == '#[] #string? _.length == 3'

# should be null or an array of strings
* match foo == '##[] #string'

This 'in-line' short-cut for validating JSON arrays is similar to how match each works. So now, complex payloads (that include arrays) can easily be validated in one step by combining validation markers like so:

* def oddSchema = { price: '#string', status: '#? _ < 3', ck: '##number', name: '#regex[0-9X]' }
* def isValidTime = read('time-validator.js')
When method get
Then match response ==
  """
  { 
    id: '#regex[0-9]+',
    count: '#number',
    odd: '#(oddSchema)',
    data: { 
      countryId: '#number', 
      countryName: '#string', 
      leagueName: '##string', 
      status: '#number? _ >= 0', 
      sportName: '#string',
      time: '#? isValidTime(_)'
    },
    odds: '#[] oddSchema'  
  }
  """

Especially note the re-use of the oddSchema both as an embedded-expression and as an array validation (on the last line).

And you can perform conditional / cross-field validations and even business-logic validations at the same time.

# optional (can be null) and if present should be an array of size greater than zero
* match $.odds == '##[_ > 0]'

# should be an array of size equal to $.count
* match $.odds == '#[$.count]'

# use a predicate function to validate each array element
* def isValidOdd = function(o){ return o.name.length == 1 }
* match $.odds == '#[]? isValidOdd(_)'

Refer to this for the complete example: schema-like.feature

And there is another example in the karate-demos: schema.feature where you can compare Karate's approach with an actual JSON-schema example. You can also find a nice visual comparison and explanation here.

contains short-cuts

Especially when payloads are complex (or highly dynamic), it may be more practical to use contains semantics. Karate has the following short-cut symbols designed to be mixed into embedded expressions:

SymbolMeans
^contains
^^contains only
^*contains any
^+contains deep
!^not contains

For completeness, == and != also belong in the above list.

Here'a table of the alternative 'in-line' forms compared with the 'standard' form. Note that all the short-cut forms on the right-side of the table resolve to 'equality' (==) matches, which enables them to be 'in-lined' into a full (single-step) payload match, using embedded expressions.

A very useful capability is to be able to check that an array contains an object that contains the provided sub-set of keys instead of having to specify the complete JSON - which can get really cumbersome for large objects. This turns out to be very useful in practice, and this particular match jsonArray contains '#(^partialObject)' form has no 'in-line' equivalent (see the third-from-last row above).

The last row in the table is a little different from the rest, and this short-cut form is the recommended way to validate the length of a JSON array. As a rule of thumb, prefer match over assert, because match failure messages are more detailed and descriptive.

In real-life tests, these are very useful when the order of items in arrays returned from the server are not guaranteed. You can easily assert that all expected elements are present, even in nested parts of your JSON - while doing a match on the full payload.

* def cat = 
  """
  {
    name: 'Billie',
    kittens: [
      { id: 23, name: 'Bob' },
      { id: 42, name: 'Wild' }
    ]
  }
  """
* def expected = [{ id: 42, name: 'Wild' }, { id: 23, name: 'Bob' }]
* match cat == { name: 'Billie', kittens: '#(^^expected)' }

There's a lot going on in the last line above ! It validates the entire payload in one step and checks if the kittens array contains all the expected items but in any order.

get

By now, it should be clear that JsonPath can be very useful for extracting JSON 'trees' out of a given object. The get keyword allows you to save the results of a JsonPath expression for later use - which is especially useful for dynamic data-driven testing.

* def cat = 
  """
  {
    name: 'Billie',
    kittens: [
      { id: 23, name: 'Bob' },
      { id: 42, name: 'Wild' }
    ]
  }
  """
* def kitnums = get cat.kittens[*].id
* match kitnums == [23, 42]
* def kitnames = get cat $.kittens[*].name
* match kitnames == ['Bob', 'Wild']

get short-cut

The 'short cut' $variableName form is also supported. Refer to JsonPath short-cuts for a detailed explanation. So the above could be re-written as follows:

* def kitnums = $cat.kittens[*].id
* match kitnums == [23, 42]
* def kitnames = $cat.kittens[*].name
* match kitnames == ['Bob', 'Wild']

It is worth repeating that the above can be condensed into 2 lines. Note that since only JsonPath is expected on the left-hand-side of the == sign of a match statement, you don't need to prefix the variable reference with $:

* match cat.kittens[*].id == [23, 42]
* match cat.kittens[*].name == ['Bob', 'Wild']

# if you prefer using 'pure' JsonPath, you can do this
* match cat $.kittens[*].id == [23, 42]
* match cat $.kittens[*].name == ['Bob', 'Wild']

get plus index

A convenience that the get syntax supports (but not the $ short-cut form) is to return a single element if the right-hand-side evaluates to a list-like result (e.g. a JSON array). This is useful because the moment you use a wildcard [*] or search filter in JsonPath (see the next section), you get an array back - even though typically you would only be interested in the first item.

* def actual = 23

# so instead of this
* def kitnums = get cat.kittens[*].id
* match actual == kitnums[0]

# you can do this in one line
* match actual == get[0] cat.kittens[*].id

JsonPath filters

JsonPath filter expressions are very useful for extracting elements that meet some filter criteria out of arrays.

* def cat = 
  """
  {
    name: 'Billie',
    kittens: [
      { id: 23, name: 'Bob' },
      { id: 42, name: 'Wild' }
    ]
  }
  """
# find single kitten where id == 23
* def bob = get[0] cat.kittens[?(@.id==23)]
* match bob.name == 'Bob'

# using the karate object if the expression is dynamic
* def temp = karate.jsonPath(cat, "$.kittens[?(@.name=='" + bob.name + "')]")
* match temp[0] == bob

# or alternatively
* def temp = karate.jsonPath(cat, "$.kittens[?(@.name=='" + bob.name + "')]")[0]
* match temp == bob

You usually won't need this, but the second-last line above shows how the karate object can be used to evaluate JsonPath if the filter expression depends on a variable. If you find yourself struggling to write dynamic JsonPath filters, look at karate.filter() as an alternative, described just below.

JSON Transforms

Karate supports the following functional-style operations via the JS API - karate.map(), karate.filter() and karate.forEach(). They can be very useful in some situations. A good example is when you have the expected data available as ready-made JSON but it is in a different "shape" from the actual data or HTTP response. There is also a karate.mapWithKey() for a common need - which is to convert an array of primitives into an array of objects, which is the form that data driven features expect.

A few more useful "transforms" are to select a sub-set of key-value pairs using karate.filterKeys(), merging 2 or more JSON-s using karate.merge() and combining 2 or more arrays (or objects) into a single array using karate.append(). And karate.appendTo() is for updating an existing variable (the equivalent of array.push() in JavaScript), which is especially useful in the body of a karate.forEach().

You can also sort arrays of arbitrary JSON using karate.sort(). Simple arrays of strings or numbers can be stripped of duplicates using karate.distinct(). All JS "native" array operations can be used, such as someName.reverse().

Note that a single JS function is sufficient to transform a given JSON object into a completely new one, and you can use complex conditional logic if needed.

Scenario: karate map operation
    * def fun = function(x){ return x * x }
    * def list = [1, 2, 3]
    * def res = karate.map(list, fun)
    * match res == [1, 4, 9]

Scenario: convert an array into a different shape
    * def before = [{ foo: 1 }, { foo: 2 }, { foo: 3 }]
    * def fun = function(x){ return { bar: x.foo } }
    * def after = karate.map(before, fun)
    * match after == [{ bar: 1 }, { bar: 2 }, { bar: 3 }]

Scenario: convert array of primitives into array of objects
    * def list = [ 'Bob', 'Wild', 'Nyan' ]
    * def data = karate.mapWithKey(list, 'name')
    * match data == [{ name: 'Bob' }, { name: 'Wild' }, { name: 'Nyan' }]

Scenario: karate filter operation
    * def fun = function(x){ return x % 2 == 0 }
    * def list = [1, 2, 3, 4]
    * def res = karate.filter(list, fun)
    * match res == [2, 4]

Scenario: forEach works even on object key-values, not just arrays
    * def keys = []
    * def vals = []
    * def idxs = []
    * def fun = 
    """
    function(x, y, i) { 
      karate.appendTo(keys, x); 
      karate.appendTo(vals, y); 
      karate.appendTo(idxs, i); 
    }
    """
    * def map = { a: 2, b: 4, c: 6 }
    * karate.forEach(map, fun)
    * match keys == ['a', 'b', 'c']
    * match vals == [2, 4, 6]
    * match idxs == [0, 1, 2]

Scenario: filterKeys
    * def schema = { a: '#string', b: '#number', c: '#boolean' }
    * def response = { a: 'x', c: true }
    # very useful for validating a response against a schema "super-set"
    * match response == karate.filterKeys(schema, response)
    * match karate.filterKeys(response, 'b', 'c') == { c: true }
    * match karate.filterKeys(response, ['a', 'b']) == { a: 'x' }

Scenario: merge
    * def foo = { a: 1 }
    * def bar = karate.merge(foo, { b: 2 })
    * match bar == { a: 1, b: 2 }

Scenario: append
    * def foo = [{ a: 1 }]
    * def bar = karate.append(foo, { b: 2 })
    * match bar == [{ a: 1 }, { b: 2 }]

Scenario: sort
    * def foo = [{a: { b: 3 }}, {a: { b: 1 }}, {a: { b: 2 }}]
    * def fun = function(x){ return x.a.b }
    * def bar = karate.sort(foo, fun)
    * match bar == [{a: { b: 1 }}, {a: { b: 2 }}, {a: { b: 3 }}]
    * match bar.reverse() == [{a: { b: 3 }}, {a: { b: 2 }}, {a: { b: 1 }}]

Loops

Given the examples above, it has to be said that a best practice with Karate is to avoid JavaScript for loops as far as possible. A common requirement is to build an array with n elements or do something n times where n is an integer (that could even be a variable reference). This is easily achieved with the karate.repeat() API:

* def fun = function(i){ return i * 2 }
* def foo = karate.repeat(5, fun)
* match foo == [0, 2, 4, 6, 8]

* def foo = []
* def fun = function(i){ karate.appendTo(foo, i) }
* karate.repeat(5, fun)
* match foo == [0, 1, 2, 3, 4]

# generate test data easily
* def fun = function(i){ return { name: 'User ' + (i + 1) } }
* def foo = karate.repeat(3, fun)
* match foo == [{ name: 'User 1' }, { name: 'User 2' }, { name: 'User 3' }]

# generate a range of numbers as a json array
* def foo = karate.range(4, 9)
* match foo == [4, 5, 6, 7, 8, 9]

And there's also karate.range() which can be useful to generate test-data.

Don't forget that Karate's data-driven testing capabilities can loop over arrays of JSON objects automatically.

XPath Functions

When handling XML, you sometimes need to call XPath functions, for example to get the count of a node-set. Any valid XPath expression is allowed on the left-hand-side of a match statement.

* def myXml =
  """
  <records>
    <record index="1">a</record>
    <record index="2">b</record>
    <record index="3" foo="bar">c</record>
  </records>
  """

* match foo count(/records//record) == 3
* match foo //record[@index=2] == 'b'
* match foo //record[@foo='bar'] == 'c'

Advanced XPath

Some XPath expressions return a list of nodes (instead of a single node). But since you can express a list of data-elements as a JSON array - even these XPath expressions can be used in match statements.

* def teachers = 
  """
  <teachers>
    <teacher department="science">
      <subject>math</subject>
      <subject>physics</subject>
    </teacher>
    <teacher department="arts">
      <subject>political education</subject>
      <subject>english</subject>
    </teacher>
  </teachers>
  """
* match teachers //teacher[@department='science']/subject == ['math', 'physics']

If your XPath is dynamic and has to be formed 'on the fly' perhaps by using some variable derived from previous steps, you can use the karate.xmlPath() helper:

* def xml = <query><name><foo>bar</foo></name></query>
* def elementName = 'name'
* def name = karate.xmlPath(xml, '/query/' + elementName + '/foo')
* match name == 'bar'
* def queryName = karate.xmlPath(xml, '/query/' + elementName)
* match queryName == <name><foo>bar</foo></name>

You can refer to this file (which is part of the Karate test-suite) for more XML examples: xml-and-xpath.feature

Special Variables

These are 'built-in' variables, there are only a few and all of them give you access to the HTTP response.

response

After every HTTP call this variable is set with the response body, and is available until the next HTTP request over-writes it. You can easily assign the whole response (or just parts of it using Json-Path or XPath) to a variable, and use it in later steps.

The response is automatically available as a JSON, XML or String object depending on what the response contents are.

As a short-cut, when running JsonPath expressions - $ represents the response. This has the advantage that you can use pure JsonPath and be more concise. For example:

# the three lines below are equivalent
Then match response $ == { name: 'Billie' }
Then match response == { name: 'Billie' }
Then match $ == { name: 'Billie' }

# the three lines below are equivalent
Then match response.name == 'Billie'
Then match response $.name == 'Billie'
Then match $.name == 'Billie'

And similarly for XML and XPath, '/' represents the response

# the four lines below are equivalent
Then match response / == <cat><name>Billie</name></cat>
Then match response/ == <cat><name>Billie</name></cat>
Then match response == <cat><name>Billie</name></cat>
Then match / == <cat><name>Billie</name></cat> 

# the three lines below are equivalent
Then match response /cat/name == 'Billie'
Then match response/cat/name == 'Billie'
Then match /cat/name == 'Billie'

JsonPath short-cuts

The $varName form is used on the right-hand-side of Karate expressions and is slightly different from pure JsonPath expressions which always begin with $. or $[. Here is a summary of what the different 'shapes' mean in Karate:

ShapeDescription
$.barPure JsonPath equivalent of $response.bar where response is a JSON object
$[0]Pure JsonPath equivalent of $response[0] where response is a JSON array
$foo.barEvaluates the JsonPath $.bar on the variable foo which is a JSON object or map-like
$foo[0]Evaluates the JsonPath $[0] on the variable foo which is a JSON array or list-like

There is no need to prefix variable names with $ on the left-hand-side of match statements because it is implied. You can if you want to, but since only JsonPath (on variables) is allowed here, Karate ignores the $ and looks only at the variable name. None of the examples in the documentation use the $varName form on the LHS, and this is the recommended best-practice.

responseBytes

This will always hold the contents of the response as a byte-array. This is rarely used, unless you are expecting binary content returned by the server. The match keyword will work as you expect. Here is an example: binary.feature.

responseCookies

The responseCookies variable is set upon any HTTP response and is a map-like (or JSON-like) object. It can be easily inspected or used in expressions.

* assert responseCookies['my.key'].value == 'someValue'

# karate's unified data handling means that even 'match' works
* match responseCookies contains { time: '#notnull' }

# ... which means that checking if a cookie does NOT exist is a piece of cake
* match responseCookies !contains { blah: '#notnull' }

# save a response cookie for later use
* def time = responseCookies.time.value

As a convenience, cookies from the previous response are collected and passed as-is as part of the next HTTP request. This is what is normally expected and simulates a web-browser - which makes it easy to script things like HTML-form based authentication into test-flows. Refer to the documentation for cookie for details and how you can disable this if need be.

Each item within responseCookies is itself a 'map-like' object. Typically you would examine the value property as in the example above, but domain and path are also available.

responseHeaders

See also match header which is what you would normally need.

But if you need to use values in the response headers - they will be in a variable named responseHeaders. Note that it is a 'map of lists' so you will need to do things like this:

* def contentType = responseHeaders['Content-Type'][0]

And just as in the responseCookies example above, you can use match to run complex validations on the responseHeaders.

Finally, using karate.responseheader() can be simpler to just get a header value string by name, and it will ignore-case for the name passed as the argument:

* match karate.header('content-type') == 'application/json'

responseStatus

You would normally only need to use the status keyword. But if you really need to use the HTTP response code in an expression or save it for later, you can get it as an integer:

* def uploadStatusCode = responseStatus

# check if the response status is either of two values
Then assert responseStatus == 200 || responseStatus == 204

Note that match can give you some extra readable options:

* match [200, 201, 204] contains responseStatus

# this may be sufficient to check a range of values
* assert responseStatus >= 200
* assert responseStatus < 300

# but using karate.range() you can even do this !
* match karate.range(200, 299) contains responseStatus

responseTime

The response time (in milliseconds) for the current response would be available in a variable called responseTime. You can use this to assert that it was returned within the expected time like so:

When method post
Then status 201
And assert responseTime < 1000

responseType

Karate will attempt to parse the raw HTTP response body as JSON or XML and make it available as the response value. If parsing fails, Karate will log a warning and the value of response will then be a plain string. You can still perform string comparisons such as a match contains and look for error messages etc. In rare cases, you may want to check what the "type" of the response is and it can be one of 3 different values: json, xml and string.

So if you really wanted to assert that the HTTP response body is well-formed JSON or XML you can do this:

When method post
Then status 201
And match responseType == 'json'

requestTimeStamp

Very rarely used - but you can get the Java system-time (for the current response) at the point when the HTTP request was initiated (the value of System.currentTimeMillis()) which can be used for detailed logging or custom framework / stats calculations.

HTTP Header Manipulation

configure headers

Custom header manipulation for every HTTP request is something that Karate makes very easy and pluggable. For every HTTP request made from Karate, the internal flow is as follows:

  • did we configure the value of headers ?
  • if so, is the configured value a JavaScript function ?
    • if so, a call is made to that function.
    • did the function invocation return a map-like (or JSON) object ?
      • all the key-value pairs are added to the HTTP headers.
  • or is the configured value a JSON object ?
    • all the key-value pairs are added to the HTTP headers.

This makes setting up of complex authentication schemes for your test-flows really easy. It typically ends up being a one-liner that appears in the Background section at the start of your test-scripts. You can re-use the function you create across your whole project.

Here is an example JavaScript function that uses some variables in the context (which have been possibly set as the result of a sign-in) to build the Authorization header. Note how even calls to Java code can be made if needed.

In the example below, note the use of the karate.get() helper for getting the value of a dynamic variable (which was not set at the time this JS function was declared). This is preferred because it takes care of situations such as if the value is undefined in JavaScript. In rare cases you may need to set a variable from this routine, and a good example is to make the generated UUID "visible" to the currently executing script or feature. You can easily do this via karate.set('someVarName', value).

function fn() {
  var uuid = '' + java.util.UUID.randomUUID(); // convert to string
  var out = { // so now the txid_header would be a unique uuid for each request
    txid_header: uuid,
    ip_header: '123.45.67.89', // hard coded here, but also can be as dynamic as you want   
  };
  var authString = '';
  var authToken = karate.get('authToken'); // use the 'karate' helper to do a 'safe' get of a 'dynamic' variable
  if (authToken) { // and if 'authToken' is not null ... 
    authString = ',auth_type=MyAuthScheme'
        + ',auth_key=' + authToken.key
        + ',auth_user=' + authToken.userId
        + ',auth_project=' + authToken.projectId;
  }
  // the 'appId' variable here is expected to have been set via karate-config.js (bootstrap init) and will never change
  out['Authorization'] = 'My_Auth app_id=' + appId + authString;
  return out;
}

Assuming the above code is in a file called my-headers.js, the next section on calling other feature files shows how it looks like in action at the beginning of a test script.

Notice how once the authToken variable is initialized, it is used by the above function to generate headers for every HTTP call made as part of the test flow.

If a few steps in your flow need to temporarily change (or completely bypass) the currently-set header-manipulation scheme, just update configure headers to a new value (or set it to null) in the middle of a script. Then use the header keyword to do a custom 'over-ride' if needed.

The karate-demo has an example showing various ways to configure or set headers: headers.feature

The karate object

A JavaScript function or Karate expression at runtime has access to a utility object in a variable named: karate. This provides the following methods:

OperationDescription
karate.abort()you can prematurely exit a Scenario by combining this with conditional logic like so: * if (condition) karate.abort() - please use sparingly ! and also see configure abortedStepsShouldPass
karate.append(... items)useful to create lists out of items (which can be lists as well), see JSON transforms
karate.appendTo(name, ... items)useful to append to a list-like variable (that has to exist) in scope, see JSON transforms - the first argument can be a reference to an array-like variable or even the name (string) of an existing variable which is list-like
karate.call(fileName, [arg])invoke a *.feature file or a JavaScript function the same way that call works (with an optional solitary argument), see call() vs read() for details
karate.callSingle(fileName, [arg])like the above, but guaranteed to run only once even across multiple features - see karate.callSingle()
karate.configure(key, value)does the same thing as the configure keyword, and a very useful example is to do karate.configure('connectTimeout', 5000); in karate-config.js - which has the 'global' effect of not wasting time if a connection cannot be established within 5 seconds
karate.distinct(list)returns only unique items out of an array of strings or numbers
karate.doc(arg)just like karate.render() but will insert the HTML into the report
karate.embed(object, mimeType)embeds the object (can be raw bytes or an image) into the JSON report output, see this example
karate.envgets the value (read-only) of the environment property 'karate.env', and this is typically used for bootstrapping configuration
karate.eval(expression)for really advanced needs, you can programmatically generate a snippet of JavaScript which can be evaluated at run-time, you can find an example here
karate.exec(command)convenient way to execute an OS specific command and return the console output e.g. karate.exec('some.exe -h') (or karate.exec(['some.exe', '-h'])) useful for calling non-Java code (that can even return data) or for starting user-interfaces to be automated, this command will block until the process terminates, also see karate.fork()
karate.extract(text, regex, group)useful to "scrape" text out of non-JSON or non-XML text sources such as HTML, group follows the Java regex rules, see this example
karate.extractAll(text, regex, group)like the above, but returns a list of text-matches
karate.fail(message)if you want to conditionally stop a test with a descriptive error message, e.g. * if (condition) karate.fail('we expected something else')
karate.featureget metadata about the currently executing feature within a test
karate.filter(list, predicate)functional-style 'filter' operation useful to filter list-like objects (e.g. JSON arrays), see example, the second argument has to be a JS function (item, [index]) that returns a boolean
karate.filterKeys(map, keys)extracts a sub-set of key-value pairs from the first argument, the second argument can be a list (or varargs) of keys - or even another JSON where only the keys would be used for extraction, example
karate.forEach(list, function)functional-style 'loop' operation useful to traverse list-like (or even map-like) objects (e.g. JSON / arrays), see example, the second argument has to be a JS function (item, [index]) for lists and (key, [value], [index]) for JSON / maps
karate.fork(map)executes an OS command, but forks a process in parallel and will not block the test like karate.exec() e.g. karate.fork({ args: ['some.exe', '-h'] }) or karate.fork(['some.exe', '-h']) - you can use a composite string as line (or the solitary argument e.g. karate.fork('some.exe -h')) instead of args, and an optional workingDir string property and env JSON / map is also supported - this returns a Command object which has operations such as waitSync() and close() if you need more control, more details here
karate.fromString(string)for advanced conditional logic for e.g. when a string coming from an external process is dynamic - and whether it is JSON or XML is not known in advance, see example
karate.get(name, [default])get the value of a variable by name (or JsonPath expression), if not found - this returns null which is easier to handle in JavaScript (than undefined), and an optional (literal / constant) second argument can be used to return a "default" value, very useful to set variables in called features that have not been pre-defined
karate.http(url)returns a convenience Http request builder class, only recommended for advanced use
karate.jsonPath(json, expression)brings the power of JsonPath into JavaScript, and you can find an example here.
karate.keysOf(object)returns only the keys of a map-like object
karate.log(... args)log to the same logger (and log file) being used by the parent process, logging can be suppressed with configure printEnabled set to false, and just like print - use comma-separated values to "pretty print" JSON or XML
karate.logger.debug(... args)access to the Karate logger directly and log in debug. Might be desirable instead of karate.log or print when looking to reduce the logs in console in your CI/CD pipeline but still retain the information for reports. See Logging for additional details.
karate.lowerCase(object)useful to brute-force all keys and values in a JSON or XML payload to lower-case, useful in some cases, see example
karate.map(list, function)functional-style 'map' operation useful to transform list-like objects (e.g. JSON arrays), see example, the second argument has to be a JS function (item, [index])
karate.mapWithKey(list, string)convenient for the common case of transforming an array of primitives into an array of objects, see JSON transforms
karate.match(actual, expected)brings the power of the fuzzy match syntax into Karate-JS, returns a JSON in the form { pass: '#boolean', message: '#string' } and you can find an example here - you can even place a full match expression like this: karate.match("each foo contains { a: '#number' }")
karate.merge(... maps)useful to merge the key-values of two (or more) JSON (or map-like) objects, see JSON transforms
karate.osreturns the operating system details as JSON, for e.g. { type: 'macosx', name: 'Mac OS X' } - useful for writing conditional logic, the possible type-s being: macosx, windows, linux and unknown
karate.pause(number)sleep time in milliseconds, relevant only for performance-testing - and will be a no-op otherwise unless configure pauseIfNotPerf is true
karate.pretty(value)return a 'pretty-printed', nicely indented string representation of the JSON value, also see: print
karate.prettyXml(value)return a 'pretty-printed', nicely indented string representation of the XML value, also see: print
karate.prevRequestfor advanced users, you can inspect the actual HTTP request after it happens, useful if you are writing a framework over Karate, refer to this example: request.feature
karate.properties[key]get the value of any Java system-property by name, useful for advanced custom configuration
karate.range(start, end, [interval])returns a JSON array of integers (inclusive), the optional third argument must be a positive integer and defaults to 1, and if start < end the order of values is reversed
karate.read(filename)the same read() function - which is pre-defined even within JS blocks, so there is no need to ever do karate.read(), and just read() is sufficient
karate.readAsBytes(filename)rarely used, like karate.readAsString - but returns a byte array
karate.readAsStream(filename)rarely used, like karate.readAsString - but returns a Java InputStream
karate.readAsString(filename)rarely used, behaves exactly like read - but does not auto convert to JSON or XML
karate.remove(name, path)very rarely used - when needing to perform conditional removal of JSON keys or XML nodes. Behaves the same way as the remove keyword.
karate.render(arg)renders an HTML template, the arg can be a string (prefixable path to the HTML) or a JSON that takes either a path or html property, see doc
karate.repeat(count, function)useful for building an array with count items or doing something count times, refer this example. Also see loops.
karate.responseHeader(string)returns the response HTTP header value (as a single string) for the given name, and will ignore-case, and can be simpler than using responseHeaders
karate.scenarioget metadata about the currently executing Scenario (or Outline - Example) within a test
karate.set(name, value)sets the value of a variable (immediately), which may be needed in case any other routines (such as the configured headers) depend on that variable
karate.set(object)where the single argument is expected to be a Map or JSON-like, and will perform the above karate.set() operation for all key-value pairs in one-shot, see example
karate.set(name, path, value)only needed when you need to conditionally build payload elements, especially XML. This is best explained via an example, and it behaves the same way as the set keyword. Also see eval.
karate.setXml(name, xmlString)rarely used, refer to the example above
karate.signal(result)trigger an event that karate.listen(timeout) is waiting for, and pass the data, see async
karate.sizeOf(object)returns the size of the map-like or list-like object
karate.sort(list, function)sorts the list using the provided custom function called for each item in the list (and the optional second argument is the item index) e.g. karate.sort(myList, x => x.val), and the second / function argument is not needed if the list is of plain strings or numbers
karate.start()only for starting a mock from within a test / feature file see mocks
karate.stop(port)will pause the test execution until a socket connection (even HTTP GET) is made to the port logged to the console, useful for troubleshooting UI tests without using a de-bugger, of course - NEVER forget to remove this after use !
karate.target(object)currently for web-ui automation only, see target lifecycle
karate.tagsfor advanced users - scripts can introspect the tags that apply to the current scope, refer to this example: tags.feature
karate.tagValuesfor even more advanced users - Karate natively supports tags in a @name=val1,val2 format, and there is an inheritance mechanism where Scenario level tags can over-ride Feature level tags, refer to this example: tags.feature
karate.toAbsolutePath(relativePath)when you want to get the absolute OS path to the argument which could even have a prefix such as classpath:, e.g. karate.toAbsolutePath('some.json')
karate.toBean(json, className)converts a JSON string or map-like object into a Java object, given the Java class name as the second argument, refer to this file for an example
karate.toCsv(list)converts a JSON array (of objects) or a list-like object into a CSV string, writing this to a file is your responsibility or you could use karate.write()
karate.toJava(function)rarely used, when you need to pass a JS function to custom Java code, typically for Async, and another edge case is to convert a JSON array or object to a Java List or Map, see example
karate.toJson(object)converts a Java object into JSON, and karate.toJson(object, true) will strip all keys that have null values from the resulting JSON, convenient for unit-testing Java code, see example
karate.typeOf(any)for advanced conditional logic when object types are dynamic and not known in advance, see example
karate.urlDecode(string)URL decode
karate.urlEncode(string)URL encode
karate.valuesOf(object)returns only the values of a map-like object (or itself if a list-like object)
karate.waitForHttp(url)will wait until the URL is ready to accept HTTP connections
karate.waitForPort(host, port)will wait until the host:port is ready to accept socket connections
karate.webSocket(url, handler)see websocket
karate.write(object, path)normally not recommended, please read this first - writes the bytes of object to a path which will always be relative to the "build" directory (typically target), see this example: embed-pdf.js - and this method returns a java.io.File reference to the file created / written to
karate.xmlPath(xml, expression)Just like karate.jsonPath() - but for XML, and allows you to use dynamic XPath if needed, see example.

Code Reuse / Common Routines

call

In any complex testing endeavor, you would find yourself needing 'common' code that needs to be re-used across multiple test scripts. A typical need would be to perform a 'sign in', or create a fresh user as a pre-requisite for the scenarios being tested.

There are two types of code that can be call-ed. *.feature files and JavaScript functions.

Calling other *.feature files

When you have a sequence of HTTP calls that need to be repeated for multiple test scripts, Karate allows you to treat a *.feature file as a re-usable unit. You can also pass parameters into the *.feature file being called, and extract variables out of the invocation result.

Here is an example of using the call keyword to invoke another feature file, loaded using the read function:

If you find this hard to understand at first, try looking at this set of examples.

Feature: which makes a 'call' to another re-usable feature

Background:
  * configure headers = read('classpath:my-headers.js')
  * def signIn = call read('classpath:my-signin.feature') { username: 'john', password: 'secret' }
  * def authToken = signIn.authToken

Scenario: some scenario
  # main test steps

Note that def can be used to assign a feature to a variable. For example look at how "creator" has been defined in the Background in this example, and used later in a call statement. This is very close to how "custom keywords" work in other frameworks. See this other example for more ideas: dsl.feature.

The contents of my-signin.feature are shown below. A few points to note:

  • Karate creates a new 'context' for the feature file being invoked but passes along all variables and configuration. This means that all your config variables and configure settings would be available to use, for example loginUrlBase in the example below.
  • When you use def in the 'called' feature, it will not over-write variables in the 'calling' feature (unless you explicitly choose to use shared scope). But note that JSON, XML, Map-like or List-like variables are 'passed by reference' which means that 'called' feature steps can update or 'mutate' them using the set keyword. Use the copy keyword to 'clone' a JSON or XML payload if needed, and refer to this example for more details: copy.feature.
  • You can add (or over-ride) variables by passing a call 'argument' as shown above. Only one JSON argument is allowed, but this does not limit you in any way as you can use any complex JSON structure. You can even initialize the JSON in a separate step and pass it by name, especially if it is complex. Observe how using JSON for parameter-passing makes things super-readable. In the 'called' feature, the argument can also be accessed using the built-in variable: __arg.
  • All variables that were defined (using def) in the 'called' script would be returned as 'keys' within a JSON-like object. Note that this includes 'built-in' variables, which means that things like the last value of response would also be present. In the example above you can see that the JSON 'envelope' returned - is assigned to the variable named signIn. And then getting hold of any data that was generated by the 'called' script is as simple as accessing it by name, for example signIn.authToken as shown above. This design has the following advantages:
    • 'called' Karate scripts don't need to use any special keywords to 'return' data and can behave like 'normal' Karate tests in 'stand-alone' mode if needed
    • the data 'return' mechanism is 'safe', there is no danger of the 'called' script over-writing any variables in the 'calling' (or parent) script (unless you use shared scope)
    • the need to explicitly 'unpack' variables by name from the returned 'envelope' keeps things readable and maintainable in the 'caller' script

Note that only variables and configuration settings will be passed. You can't do things such as * url 'http://foo.bar' and expect the URL to be set in the "called" feature. Use a variable in the "called" feature instead, for e.g. * url myUrl.

Feature: here are the contents of 'my-signin.feature'

Scenario:
  Given url loginUrlBase
  And request { userId: '#(username)', userPass: '#(password)' }
  When method post
  Then status 200
  And def authToken = response

  # second HTTP call, to get a list of 'projects'
  Given path 'users', authToken.userId, 'projects'
  When method get
  Then status 200
  # logic to 'choose' first project
  And set authToken.projectId = response.projects[0].projectId;

The above example actually makes two HTTP requests - the first is a standard 'sign-in' POST and then (for illustrative purposes) another HTTP call (a GET) is made for retrieving a list of projects for the signed-in user, and the first one is 'selected' and added to the returned 'auth token' JSON object.

So you get the picture, any kind of complicated 'sign-in' flow can be scripted and re-used.

If the second HTTP call above expects headers to be set by my-headers.js - which in turn depends on the authToken variable being updated, you will need to duplicate the line * configure headers = read('classpath:my-headers.js') from the 'caller' feature here as well. The above example does not use shared scope, which means that the variables in the 'calling' (parent) feature are not shared by the 'called' my-signin.feature. The above example can be made more simpler with the use of call (or callonce) without a def-assignment to a variable, and is the recommended pattern for implementing re-usable authentication setup flows.

Do look at the documentation and example for configure headers also as it goes hand-in-hand with call. In the above example, the end-result of the call to my-signin.feature resulted in the authToken variable being initialized. Take a look at how the configure headers example uses the authToken variable.

Call Tag Selector

You can "select" a single Scenario (or Scenario-s or Scenario Outline-s or even specific Examples rows) by appending a "tag selector" at the end of the feature-file you are calling. For example:

call read('classpath:my-signin.feature@name=someScenarioName')

While the tag does not need to be in the @key=value form, it is recommended for readability when you start getting into the business of giving meaningful names to your Scenario-s.

This "tag selection" capability is designed for you to be able to "compose" flows out of existing test-suites when using the Karate Gatling integration. Normally we recommend that you keep your "re-usable" features lightweight - by limiting them to just one Scenario.

Call Same Feature

As a convenience, you can call a tag directly, which is a short-cut to call another Scenario within the same feature file. Note that you would typically want to use the @ignore tag for such cases.

Scenario: one
* call read('@two')

@ignore @two
Scenario: two
* print 'called'

Data-Driven Features

If the argument passed to the call of a *.feature file is a JSON array, something interesting happens. The feature is invoked for each item in the array. Each array element is expected to be a JSON object, and for each object - the behavior will be as described above.

But this time, the return value from the call step will be a JSON array of the same size as the input array. And each element of the returned array will be the 'envelope' of variables that resulted from each iteration where the *.feature got invoked.

Here is an example that combines the table keyword with calling a *.feature. Observe how the get shortcut is used to 'distill' the result array of variable 'envelopes' into an array consisting only of response payloads.

* table kittens 
  | name   | age |
  | 'Bob'  |   2 |
  | 'Wild' |   1 |
  | 'Nyan' |   3 |

* def result = call read('cat-create.feature') kittens
* def created = $result[*].response
* match each created == { id: '#number', name: '#string', age: '#number' }
* match created[*].name contains only ['Bob', 'Wild', 'Nyan']

And here is how cat-create.feature could look like:

@ignore
Feature:

Scenario:
  Given url someUrlFromConfig
  And path 'cats'
  And request { name: '#(name)', age: '#(age)' }
  When method post
  Then status 200

If you replace the table with perhaps a JavaScript function call that gets some JSON data from some data-source, you can imagine how you could go about dynamic data-driven testing.

Although it is just a few lines of code, take time to study the above example carefully. It is a great example of how to effectively use the unique combination of Cucumber and JsonPath that Karate provides.

Also look at the demo examples, especially dynamic-params.feature - to compare the above approach with how the Cucumber Scenario Outline: can be alternatively used for data-driven tests.

Built-in variables for call

Although all properties in the passed JSON-like argument are 'unpacked' into the current scope as separate 'named' variables, it sometimes makes sense to access the whole argument and this can be done via __arg. And if being called in a loop, a built-in variable called __loop will also be available that will hold the value of the current loop index. So you can do things like this: * def name = name + __loop - or you can use the loop index value for looking up other values that may be in scope - in a data-driven style.

VariableRefers To
__argthe single call (or callonce) argument, will be null if there was none
__loopthe current iteration index (starts from 0) if being called in a loop, will be -1 if not

Refer to this demo feature for an example: kitten-create.feature

Default Values

Some users need "callable" features that are re-usable even when variables have not been defined by the calling feature. Normally an undefined variable results in nasty JavaScript errors. But there is an elegant way you can specify a default value using the karate.get() API:

# if foo is not defined, it will default to 42
* def foo = karate.get('foo', 42)

A word of caution: we recommend that you should not over-use Karate's capability of being able to re-use features. Re-use can sometimes result in negative benefits - especially when applied to test-automation. Prefer readability over re-use. See this for an example.

copy

For a call (or callonce) - payload / data structures (JSON, XML, Map-like or List-like) variables are 'passed by reference' which means that steps within the 'called' feature can update or 'mutate' them, for e.g. using the set keyword. This is actually the intent most of the time and is convenient. If you want to pass a 'clone' to a 'called' feature, you can do so using the rarely used copy keyword that works very similar to type conversion. This is best explained in this example: copy.feature.

Calling JavaScript Functions

Examples of defining and using JavaScript functions appear in earlier sections of this document. Being able to define and re-use JavaScript functions is a powerful capability of Karate. For example, you can:

  • call re-usable functions that take complex data as an argument and return complex data that can be stored in a variable
  • call and interoperate with Java code if needed
  • share and re-use test utilities or 'helper' functionality across your organization

For an advanced example of how you can build and re-use a common set of JS functions, refer to this answer on Stack Overflow.

In real-life scripts, you would typically also use this capability of Karate to configure headers where the specified JavaScript function uses the variables that result from a sign in to manipulate headers for all subsequent HTTP requests. And it is worth mentioning that the Karate configuration 'bootstrap' routine is itself a JavaScript function.

Also refer to the eval keyword for a simpler way to execute arbitrary JavaScript that can be useful in some situations.

JS function argument rules for call

When using call (or callonce), only one argument is allowed. But this does not limit you in any way, because similar to how you can call *.feature files, you can pass a whole JSON object as the argument. In the case of the call of a JavaScript function, you can also pass a JSON array or a primitive (string, number, boolean) as the solitary argument, and the function implementation is expected to handle whatever is passed.

Instead of using call (or callonce) you are always free to call JavaScript functions 'normally' and then you can use more than one argument.

* def adder = function(a, b){ return a + b }
* assert adder(1, 2) == 3

Return types

Naturally, only one value can be returned. But again, you can return a JSON object. There are two things that can happen to the returned value.

Either - it can be assigned to a variable like so.

* def returnValue = call myFunction

Or - if a call is made without an assignment, and if the function returns a map-like object, it will add each key-value pair returned as a new variable into the execution context.

# while this looks innocent ...
# ... behind the scenes, it could be creating (or over-writing) a bunch of variables !
* call someFunction

While this sounds dangerous and should be used with care (and limits readability), the reason this feature exists is to quickly set (or over-write) a bunch of config variables when needed. In fact, this is the mechanism used when karate-config.js is processed on start-up.

Shared Scope

This behavior where all key-value pairs in the returned map-like object get automatically added as variables - applies to the calling of *.feature files as well. In other words, when call or callonce is used without a def, the 'called' script not only shares all variables (and configure settings) but can update the shared execution context. This is very useful to boil-down those 'common' steps that you may have to perform at the start of multiple test-scripts - into one-liners. But use wisely, because called scripts will now over-write variables that may have been already defined.

* def config = { user: 'john', password: 'secret' }
# this next line may perform many steps and result in multiple variables set for the rest of the script
* call read('classpath:common-setup.feature') config

You can use callonce instead of call within the Background in case you have multiple Scenario sections or Examples. Note the 'inline' use of the read function as a short-cut above. This applies to JS functions as well:

* call read('my-function.js')

These heavily commented demo examples can help you understand 'shared scope' better, and are designed to get you started with creating re-usable 'sign-in' or authentication flows:

ScopeCaller FeatureCalled Feature
Isolatedcall-isolated-headers.featurecommon-multiple.feature
Sharedcall-updates-config.featurecommon.feature

Once you get comfortable with Karate, you can consider moving your authentication flow into a 'global' one-time flow using karate.callSingle(), think of it as 'callonce on steroids'.

call vs read()

Since this is a frequently asked question, the different ways of being able to re-use code (or data) are summarized below.

CodeDescription
* def login = read('login.feature')
* call login
Shared Scope, and the 
login variable can be re-used
* call read('login.feature')short-cut for the above 
without needing a variable
* def credentials = read('credentials.json')
* def login = read('login.feature')
* call login credentials
Note how using read() 
for a JSON file returns data
not "callable" code, and here it is 
used as the call argument
* call read('login.feature') read('credentials.json')You can do this in theory, 
but it is not as readable as the above
* karate.call('login.feature')The JS API allows you to do this, 
but this will not be Shared Scope
* def result = call read('login.feature')call result assigned to a variable 
and not Shared Scope
* def result = karate.call('login.feature')exactly equivalent to the above !
* if (cond) karate.call(true, 'login.feature')if you need conditional logic 
and Shared Scope, add a 
boolean true first argument
* def credentials = read('credentials.json')
* def result = call read('login.feature') credentials
like the above, 
but with a call argument
* def credentials = read('credentials.json')
* def result = karate.call('login.feature', credentials)
like the above, but in JS API form, 
the advantage of the above form is 
that using an in-line argument is less 
"cluttered" (see next row)
* def login = read('login.feature')
* def result = call login { user: 'john', password: 'secret' }
using the call keyword makes 
passing an in-line JSON argument 
more "readable"
* call read 'credentials.json'Since "read" happens to be a 
function (that takes a single 
string argument), this has the effect 
of loading all keys in the JSON file
into Shared Scope as variables
This can be sometimes handy.
* call read ('credentials.json')A common mistake. First, there 
is no meaning in call for JSON. 
Second, the space after the "read
makes this equal to the above.
* karate.set(read('credentials.json'))For completeness - this has exactly the same effect as the above two rows !

Calling Java

There are examples of calling JVM classes in the section on Java Interop and in the file-upload demo. Also look at the section on commonly needed utilities for more ideas.

Calling any Java code is that easy. Given this custom, user-defined Java class:

package com.mycompany;

import java.util.HashMap;
import java.util.Map;

public class JavaDemo {    
    
    public Map<String, Object> doWork(String fromJs) {
        Map<String, Object> map = new HashMap<>();
        map.put("someKey", "hello " + fromJs);
        return map;
    }

    public static String doWorkStatic(String fromJs) {
        return "hello " + fromJs;
    }   

}

This is how it can be called from a test-script via JavaScript, and yes, even static methods can be invoked:

* def doWork =
  """
  function(arg) {
    var JavaDemo = Java.type('com.mycompany.JavaDemo');
    var jd = new JavaDemo();
    return jd.doWork(arg);  
  }
  """
# in this case the solitary 'call' argument is of type string
* def result = call doWork 'world'
* match result == { someKey: 'hello world' }

# using a static method - observe how java interop is truly seamless !
* def JavaDemo = Java.type('com.mycompany.JavaDemo')
* def result = JavaDemo.doWorkStatic('world')
* assert result == 'hello world'

Note that JSON gets auto-converted to Map (or List) when making the cross-over to Java. Refer to the cats-java.feature demo for an example.

An additional-level of auto-conversion happens when objects cross the boundary between JS and Java. In the rare case that you need to mutate a Map or List returned from Java but while still within a JS block, use karate.toJson() to convert.

Another example is dogs.feature - which actually makes JDBC (database) calls, and since the data returned from the Java code is JSON, the last section of the test is able to use match very effectively for data assertions.

A great example of how you can extend Karate, even bypass the HTTP client but still use Karate's test-automation effectively, is this gRPC example by @thinkerou: karate-grpc. And you can even handle asynchronous flows such as listening to message-queues.

HTTP Basic Authentication Example

This should make it clear why Karate does not provide 'out of the box' support for any particular HTTP authentication scheme. Things are designed so that you can plug-in what you need, without needing to compile Java code. You get to choose how to manage your environment-specific configuration values such as user-names and passwords.

First the JavaScript file, basic-auth.js:

function fn(creds) {
  var temp = creds.username + ':' + creds.password;
  var Base64 = Java.type('java.util.Base64');
  var encoded = Base64.getEncoder().encodeToString(temp.toString().getBytes());
  return 'Basic ' + encoded;
}

And here's how it works in a test-script using the header keyword.

* header Authorization = call read('basic-auth.js') { username: 'john', password: 'secret' }

You can set this up for all subsequent requests or dynamically generate headers for each HTTP request if you configure headers.

callonce

Cucumber has a limitation where Background steps are re-run for every Scenario. And if you have a Scenario Outline, this happens for every row in the Examples. This is a problem especially for expensive, time-consuming HTTP calls, and this has been an open issue for a long time.

Karate's callonce keyword behaves exactly like call but is guaranteed to execute only once. The results of the first call are cached, and any future calls will simply return the cached result instead of executing the JavaScript function (or feature) again and again.

This does require you to move 'set-up' into a separate *.feature (or JavaScript) file. But this totally makes sense for things not part of the 'main' test flow and which typically need to be re-usable anyway.

So when you use the combination of callonce in a Background, you can indeed get the same effect as using a @BeforeClass annotation, and you can find examples in the karate-demo, such as this one: callonce.feature.

A callonce is ideally used for only "pure" JSON. You may face issues if you attempt to mix in JS functions or Java code. See karate.callSingle().

eval

This is for evaluating arbitrary JavaScript and you are advised to use this only as a last resort ! Conditional logic is not recommended especially within test scripts because tests should be deterministic.

There are a few situations where this comes in handy:

# just perform an action, we don't care about saving the result
* eval myJavaScriptFunction()

# do something only if a condition is true
* eval if (zone == 'zone1') karate.set('temp', 'after')

As a convenience, you can omit the eval keyword and so you can shorten the above to:

* myJavaScriptFunction()
* if (zone == 'zone1') karate.set('temp', 'after')

This is very convenient especially if you are calling a method on a variable that has been defined such as the karate object, and for general-purpose scripting needs such as UI automation. Note how karate.set() and karate.remove() below are used directly as a script "statement".

# you can use multiple lines of JavaScript if needed
* eval
  """
  var foo = function(v){ return v * v };
  var nums = [0, 1, 2, 3, 4];
  var squares = [];
  for (var n in nums) {
    squares.push(foo(n));
  }
  karate.set('temp', squares);
  """
* match temp == [0, 1, 4, 9, 16]

* def json = { a: 1 }
* def key = 'b'
# use dynamic path expressions to mutate json
* json[key] = 2
* match json == { a: 1, b: 2 }
* karate.remove('json', key)
* match json == { a: 1 }
* karate.set('json', '$.c[]', { d: 'e' })
* match json == { a: 1, c: [{ d: 'e' }] }

Advanced / Tricks

Polling

The built-in retry until syntax should suffice for most needs, but if you have some specific needs, this demo example (using JavaScript) should get you up and running: polling.feature.

Conditional Logic

The keywords Given When Then are only for decoration and should not be thought of as similar to an if - then - else statement. And as a testing framework, Karate discourages tests that give different results on every run.

That said, if you really need to implement 'conditional' checks, this can be one pattern:

* def filename = zone == 'zone1' ? 'test1.feature' : 'test2.feature'
* def result = call read(filename)

And this is another, using karate.call(). Here we want to call a file only if a condition is satisfied:

* def result = responseStatus == 404 ? {} : karate.call('delete-user.feature')

Or if we don't care about the result, we can eval an if statement:

* if (responseStatus == 200) karate.call('delete-user.feature')

And this may give you more ideas. You can always use a JavaScript function or call Java for more complex logic.

* def expected = zone == 'zone1' ? { foo: '#string' } : { bar: '#number' }
* match response == expected

JSON Lookup

You can always use a JavaScript switch case within an eval or function block. But one pattern that you should be aware of is that JSON is actually a great data-structure for looking up data.

* def data =
"""
{
   foo: 'hello',
   bar: 'world'  
}
"""
# in real-life key can be dynamic
* def key = 'bar'
# and used to lookup data
* match (data[key]) == 'world'

You can find more details here. Also note how you can wrap the LHS of the match in parentheses in the rare cases where the parser expects JsonPath by default.

Abort and Fail

In some rare cases you need to exit a Scenario based on some condition. You can use karate.abort() like so:

* if (responseStatus == 404) karate.abort()

Using karate.abort() will not fail the test. Conditionally making a test fail is easy with karate.fail()

* if (condition) karate.fail('a custom message')

But normally a match statement is preferred unless you want a really descriptive error message.

Also refer to polling for more ideas.

Commonly Needed Utilities

Since it is so easy to dive into Java-interop, Karate does not include any random-number functions, uuid generator or date / time utilities out of the box. You simply roll your own.

Here is an example of how to get the current date, and formatted the way you want:

* def getDate =
  """
  function() {
    var SimpleDateFormat = Java.type('java.text.SimpleDateFormat');
    var sdf = new SimpleDateFormat('yyyy/MM/dd');
    var date = new java.util.Date();
    return sdf.format(date);
  } 
  """

* def temp = getDate()
* print temp

And the above will result in something like this being logged: [print] 2017/10/16.

Here below are a few more common examples:

UtilityRecipe
System Time (as a string)function(){ return java.lang.System.currentTimeMillis() + '' }
UUIDfunction(){ return java.util.UUID.randomUUID() + '' }
Random Number (0 to max-1)function(max){ return Math.floor(Math.random() * max) }
Case Insensitive Comparisonfunction(a, b){ return a.equalsIgnoreCase(b) }
Sleep or Wait for pause millisecondsfunction(pause){ java.lang.Thread.sleep(pause) }

The first three are good enough for random string generation for most situations. Note that if you need to do a lot of case-insensitive string checks, karate.lowerCase() is what you are looking for.

Multiple Functions in One File

If you find yourself needing a complex helper or utility function, we strongly recommend that you use Java because it is much easier to maintain and even debug if needed. And if you need multiple functions, you can easily organize them into a single Java class with multiple static methods.

That said, if you want to stick to JavaScript, but find yourself accumulating a lot of helper functions that you need to use in multiple feature files, the following pattern is recommended.

You can organize multiple "common" utilities into a single re-usable feature file as follows e.g. common.feature

@ignore
Feature:

Scenario:
  * def hello = function(){ return 'hello' }
  * def world = function(){ return 'world' }

And then you have two options. The first option using shared scope should be fine for most projects, but if you want to "name space" your functions, use "isolated scope":

Scenario: function re-use, global / shared scope
    * call read('common.feature')
    * assert hello() == 'hello'
    * assert world() == 'world'

Scenario: function re-use, isolated / name-spaced scope
    * def utils = call read('common.feature')
    * assert utils.hello() == 'hello'
    * assert utils.world() == 'world'

You can even move commonly used routines into karate-config.js which means that they become "global". But we recommend that you do this only if you are sure that these routines are needed in almost all *.feature files. Bloating your configuration can lead to loss of performance, and maintainability may suffer.

Async

The JS API has a karate.signal(result) method that is useful for involving asynchronous flows into a test.

listen

You use the listen keyword (with a timeout) to wait until that event occurs. The listenResult magic variable will hold the value passed to the call to karate.signal().

This is best explained in this example that involves listening to an ActiveMQ / JMS queue.

Note how JS functions defined at run-time can be mixed with custom Java code to get things done. You need to use karate.toJava() to "wrap" JS functions passed to custom Java code.

Background:
* def QueueConsumer = Java.type('mock.contract.QueueConsumer')
* def queue = new QueueConsumer(queueName)
* def handler = function(msg){ karate.signal(msg) }
* queue.listen(karate.toJava(handler))
* url paymentServiceUrl + '/payments'

Scenario: create, get, update, list and delete payments
    Given request { amount: 5.67, description: 'test one' }
    When method post
    Then status 200
    And match response == { id: '#number', amount: 5.67, description: 'test one' }
    And def id = response.id
    * listen 5000
    * json shipment = listenResult
    * print '### received:', shipment
    * match shipment == { paymentId: '#(id)', status: 'shipped' }

Java Function References

JavaScript functions have some limitations when combined with multi-threaded Java code. So it is recommended that you directly use a Java Function when possible instead of using the karate.toJava() "wrapper" as shown above.

One pattern you can adopt is to create a "factory" method that returns a Java function - where you can easily delegate to the logic you want. For example, see the sayHelloFactory() method below:

public class Hello {

    public static String sayHello(String message) {
        return "hello " + message;
    }

    public static Function<String, String> sayHelloFactory() {
        return s -> sayHello(s);
    }

}

And now, to get a reference to that "function" you can do this:

* def sayHello = Java.type('com.myco.Hello').sayHelloFactory()

This can be convenient when using shared scope because you can just call sayHello('myname') where needed.

WebSocket

Karate also has built-in support for websocket that is based on the async capability. The following method signatures are available on the karate JS object to obtain a websocket reference:

  • karate.webSocket(url)
  • karate.webSocket(url, handler)
  • karate.webSocket(url, handler, options) - where options is an optional JSON (or map-like) object that takes the following optional keys:
    • subProtocol - in case the server expects it
    • headers - another JSON of key-value pairs
    • maxPayloadSize - this defaults to 4194304 (bytes, around 4 MB)

These will init a websocket client for the given url and optional subProtocol. If a handler function (returning a boolean) is provided - it will be used to complete the "wait" of socket.listen() if true is returned - where socket is the reference to the websocket client returned by karate.webSocket(). A handler function is needed only if you have to ignore other incoming traffic. If you need custom headers for the websocket handshake, use JSON as the last argument.

Here is an example, where the same websocket connection is used to send as well as receive a message.

* def handler = function(msg){ return msg.startsWith('hello') }
* def socket = karate.webSocket(demoBaseUrl + '/websocket', handler)
* socket.send('Billie')
* def result = socket.listen(5000)
* match result == 'hello Billie !'

For handling binary messages, the same karate.webSocket() method signatures exist for karate.webSocketBinary(). Refer to these examples for more: echo.feature | websocket.feature. Note that any websocket instances created will be auto-closed at the end of the Scenario.

Tags

Gherkin has a great way to sprinkle meta-data into test-scripts - which gives you some interesting options when running tests in bulk. The most common use-case would be to partition your tests into 'smoke', 'regression' and the like - which enables being able to selectively execute a sub-set of tests.

The documentation on how to run tests via the command line has an example of how to use tags to decide which tests to not run (or ignore). Also see first.feature and second.feature in the demos. If you find yourself juggling multiple tags with logical AND and OR complexity, refer to this Stack Overflow answer.

For advanced users, Karate supports being able to query for tags within a test, and even tags in a @name=value form. Refer to karate.tags and karate.tagValues.

Special Tags

For completeness, the "built-in" tags are the following:

TagDescription
@ignoreAny Scenario with (or that has inherited) this tag will be skipped at run-time. This does not apply to anything that is "called" though
@parallelSee @parallel=false
@reportSee @report=false
@envSee below
@envnotSee below

Environment Tags

There are two special tags that allow you to "select" or "un-select" a Scenario depending on the value of karate.env. This can be really convenient, for example to never run some tests in a certain "production like" or sensitive environment.

  • @env=foo,bar - will run only when the value of karate.env is not-null and equal to foo or bar
  • @envnot=foo - will run when the value of karate.env is null or anything other than foo

Here is an example:

@env=dev  
Scenario: runs only when karate.env is 'dev'
* print 'karate.env is:', karate.env

Since multiple values are supported, you can also do this:

@envnot=perf,prod  
Scenario: never runs in perf or prod
* print 'karate.env is:', karate.env

Tags And Examples

A little-known capability of the Cucumber / Gherkin syntax is to be able to tag even specific rows in a bunch of examples ! You have to repeat the Examples section for each tag. The example below combines this with the advanced features described above.

Scenario Outline: examples partitioned by tag
* def vals = karate.tagValues
* match vals.region[0] == expected

  @region=US
  Examples:
    | expected |
    | US       |

  @region=GB
  Examples:
    | expected |
    | GB       |

Note that if you tag Examples like this, and if a tag selector is used when running a given Feature - only the Examples that match the tag selector will be executed. There is no concept of a "default" where for e.g. if there is no matching tag - that the Examples without a tag will be executed. But note that you can use the negative form of a tag selector: ~@region=GB.

Dynamic Port Numbers

In situations where you start an (embedded) application server as part of the test set-up phase, a typical challenge is that the HTTP port may be determined at run-time. So how can you get this value injected into the Karate configuration ?

It so happens that the karate object has a field called properties which can read a Java system-property by name like this: karate.properties['myName']. Since the karate object is injected within karate-config.js on start-up, it is a simple and effective way for other processes within the same JVM to pass configuration values to Karate at run-time. Refer to the 'demo' karate-config.js for an example and how the demo.server.port system-property is set-up in the test runner: TestBase.java.

Java API

Karate has a set of Java API-s that expose the HTTP, JSON, data-assertion and UI automation capabilities. The primary classes are described below.

  • Http - build and execute any HTTP request and retrieve responses
  • Json - build and manipulate JSON data using JsonPath expressions, convert to and from Java Map-s and List-s, parse strings into JSON and convert Java objects into JSON
  • Match - exposes all of Karate's match capabilities, and this works for Java Map and List objects
  • Driver - perform web-browser automation

Do note that if you choose the Java API, you will naturally lose some of the test-automation framework benefits such as HTML reports, parallel execution and JavaScript / configuration. You may have to rely on unit-testing frameworks or integrate additional dependencies.

jbang

jbang is a great way for you to install and execute scripts that use Karate's Java API on any machine with minimal setup. Note that jbang itself is super-easy to install and there is even a "Zero Install" option.

Here below is an example jbang script that uses the Karate Java API to do some useful work. Name the file as javadsl.java and run using the command: jbang javadsl.java.

please replace RELEASE with the exact version of Karate you intend to use if applicable

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.intuit.karate:karate-core:RELEASE:all

import com.intuit.karate.*;
import java.util.List;

public class javadsl {

    public static void main(String[] args) {
        List users = Http.to("https://jsonplaceholder.typicode.com/users")
                .get().json().asList();
        Match.that(users.get(0)).contains("{ name: 'Leanne Graham' }");
        String city = Json.of(users).get("$[0].address.city");
        Match.that("Gwenborough").isEqualTo(city);
        System.out.println("\n*** second user: " + Json.of(users.get(1)).toString());
    }

}

Read the documentation of the stand-alone JAR for more - such as how you can even install custom command-line applications using jbang !

Invoking feature files using the Java API

It is also possible to invoke a feature file via a Java API which can be useful in some test-automation situations.

A common use case is to mix API-calls into a larger test-suite, for example a Selenium or WebDriver UI test. So you can use Karate to set-up data via API calls, then run the UI test-automation, and finally again use Karate to assert that the system-state is as expected. Note that you can even include calls to a database from Karate using Java interop. And this example may make it clear why using Karate itself to drive even your UI-tests may be a good idea.

The static method com.intuit.karate.Runner.runFeature() is best explained in this demo unit-test: JavaApiTest.java.

You can optionally pass in variable values or over-ride config via a HashMap or leave the second-last argument as null. The variable state after feature execution would be returned as a Map<String, Object>. The last boolean argument is whether the karate-config.js should be processed or not. Refer to the documentation on type-conversion to make sure you can 'unpack' data returned from Karate correctly, especially when dealing with XML.

Hooks

If you are looking for Cucumber 'hooks' Karate does not support them, mainly because they depend on Java code, which goes against the Karate Way™.

Instead, Karate gives you all you need as part of the syntax. Here is a summary:

To Run Some CodeHow
Before everything (or 'globally' once)See karate.callSingle()
Before every ScenarioUse the Background. Note that karate-config.js is processed before every Scenario - so you can choose to put "global" config here, for example using karate.configure().
Once (or at the start of) every FeatureUse a callonce in the Background. The advantage is that you can set up variables (using def if needed) which can be used in all Scenario-s within that Feature.
After every Scenarioconfigure afterScenario (see example)
At the end of the Featureconfigure afterFeature (see example)

Note that for the afterFeature hook to work, you should be using the Runner API and not the JUnit runner.

karate.callSingle()

Only recommended for advanced users, but this guarantees a routine is run only once, even when running tests in parallel. You can use karate.callSingle() in karate-config.js like this:

var result = karate.callSingle('classpath:some/package/my.feature');

It can take a second JSON argument following the same rules as call. Once you get a result, you typically use it to set global variables.

Refer to this example:

You can use karate.callSingle() directly in a *.feature file, but it logically fits better in the global "bootstrap". Ideally it should return "pure JSON" and note that you always get a "deep clone" of the cached result object.

IMPORTANT: There are some restrictions when using callonce or karate.callSingle() especially within karate-config.js. Ideally you should return only pure JSON data (or a primitive string, number etc.). Keep in mind that the reason this exists is to "cache" data, and not behavior. So if you return complex objects such as a custom Java instance or a JS function that depends on complex objects, this may cause issues when you run in parallel. If you really need to re-use a Java function, see Java Function References.

configure callSingleCache

When re-running tests in development mode and when your test suite depends on say an Authorization header set by karate.callSingle(), you can cache the results locally to a file, which is very convenient when your "auth token" is valid for a period of a few minutes - which typically is the case. This means that as long as the token "on file" is valid, you can save time by not having to make the one or two HTTP calls needed to "sign-in" or create "throw-away" users in your SSO store.

So in "dev mode" you can easily set this behavior like this. Just ensure that this is "configured" before you use karate.callSingle():

if (karate.env == 'local') {
  karate.configure('callSingleCache', { minutes: 15 });
}

By default Karate will use target (or build) as the "cache" folder, which you can over-ride by adding a dir key:

  karate.configure('callSingleCache', { minutes: 15, dir: 'some/other/folder' });

This caching behavior will work only if the result of karate.callSingle() is a JSON-like object, and any JS functions or Java objects mixed in will be lost.

Data Driven Tests

The Cucumber Way

Cucumber has a concept of Scenario Outlines where you can re-use a set of data-driven steps and assertions, and the data can be declared in a very user-friendly fashion. Observe the usage of Scenario Outline: instead of Scenario:, and the new Examples: section.

You should take a minute to compare this with the exact same example implemented in REST-assured and TestNG. Note that this example only does a "string equals" check on parts of the JSON, but with Karate you are always encouraged to match the entire payload in one step.

Feature: karate answers 2

Background:
  * url 'http://localhost:8080'

Scenario Outline: given circuit name, validate country
  Given path 'api/f1/circuits/<name>.json'
  When method get
  Then match $.MRData.CircuitTable.Circuits[0].Location.country == '<country>'

  Examples:
    | name   | country  |
    | monza  | Italy    |
    | spa    | Belgium  |
    | sepang | Malaysia |

Scenario Outline: given race number, validate number of pitstops for Max Verstappen in 2015
  Given path 'api/f1/2015/<race>/drivers/max_verstappen/pitstops.json'
  When method get
  Then assert response.MRData.RaceTable.Races[0].PitStops.length == <stops>

  Examples:
    | race | stops |
    | 1    | 1     |
    | 2    | 3     |
    | 3    | 2     |
    | 4    | 2     |

This is great for testing boundary conditions against a single end-point, with the added bonus that your test becomes even more readable. This approach can certainly enable product-owners or domain-experts who are not programmer-folk, to review, and even collaborate on test-scenarios and scripts.

Scenario Outline Enhancements

Karate has enhanced the Cucumber Scenario Outline as follows:

  • Type Hints: if the Examples column header has a ! appended, each value will be evaluated as a JavaScript data-type (number, boolean, or even in-line JSON) - else it defaults to string.
  • Magic Variables: __row gives you the entire row as a JSON object, and __num gives you the row index (the first row is 0).
  • Auto Variables: in addition to __row, each column key-value will be available as a separate variable, which greatly simplifies JSON manipulation - especially when you want to re-use JSON files containing embedded expressions.
  • Any empty cells will result in a null value for that column-key, and this can be useful to remove nodes from JSON or XML documents

These are best explained with examples. You can choose between the string-placeholder style <foo> or directly refer to the variable foo (or even the whole row JSON as __row) in JSON-friendly expressions.

Note that even the scenario name can accept placeholders - which is very useful in reports.

Scenario Outline: name is <name> and age is <age>
  * def temp = '<name>'
  * match temp == name
  * match temp == __row.name
  * def expected = __num == 0 ? 'name is Bob and age is 5' : 'name is Nyan and age is 6'
  * match expected == karate.scenario.name

  Examples:
    | name | age |
    | Bob  | 5   |
    | Nyan | 6   |

Scenario Outline: magic variables with type hints
  * def expected = [{ name: 'Bob', age: 5 }, { name: 'Nyan', age: 6 }]
  * match __row == expected[__num]

  Examples:
    | name | age! |
    | Bob  | 5    |
    | Nyan | 6    |

Scenario Outline: embedded expressions and type hints
  * match __row == { name: '#(name)', alive: '#boolean' }

  Examples:
    | name | alive! |
    | Bob  | false  |
    | Nyan | true   |

Scenario Outline: inline json
  * match __row == { first: 'hello', second: { a: 1 } }
  * match first == 'hello'
  * match second == { a: 1 }

  Examples:
    | first  | second!  |
    | hello  | { a: 1 } |

For another example, see: examples.feature.

If you're looking for more complex ways of dynamically naming your scenarios you can use JS string interpolation by including placeholders in your scenario name.

Scenario Outline: name is ${name.first} ${name.last} and age is ${age}
  * match name.first == "#? _ == 'Bob' || _ == 'Nyan'"
  * match name.last == "#? _ == 'Dylan' || _ == 'Cat'"
  * match title == karate.scenario.name

Examples:
  | name!                               | age | title                           |
  | { "first": "Bob", "last": "Dylan" } | 10  | name is Bob Dylan and age is 10 |
  | { "first": "Nyan", "last": "Cat" }  | 5   | name is Nyan Cat and age is 5   |

String interpolation will support variables in scope and / or the Examples (including functions defined globally, but not functions defined in the background). Even Java interop and access to the karate JS API would work.

For some more examples check test-outline-name-js.feature.

The Karate Way

The limitation of the Cucumber Scenario Outline: (seen above) is that the number of rows in the Examples: is fixed. But take a look at how Karate can loop over a *.feature file for each object in a JSON array - which gives you dynamic data-driven testing, if you need it. For advanced examples, refer to some of the scenarios within this demo: dynamic-params.feature.

Also see the option below, where you can data-drive an Examples: table using JSON.

Dynamic Scenario Outline

You can feed an Examples table from a custom data-source, which is great for those situations where the table-content is dynamically resolved at run-time. This capability is triggered when the table consists of a single "cell", i.e. there is exactly one row and one column in the table.

This technique has one caveat to be aware of regarding isolation of tests running in parallel. The Background section is only run once in order to set up the list of dynamic scenarios. This means that any other steps within the Background are not repeated for each individual example. This is different behaviour from normal scenarios where each Scenario also runs the Background steps.

JSON Array Data Source

The "scenario expression" result is expected to be an array of JSON objects. Here is an example (also see this video):

Feature: scenario outline using a dynamic table

Background:
    * def kittens = read('../callarray/kittens.json')

Scenario Outline: cat name: <name>
    Given url demoBaseUrl
    And path 'cats'
    And request { name: '#(name)' }
    When method post
    Then status 200
    And match response == { id: '#number', name: '#(name)' }

    # the single cell can be any valid karate expression
    # and even reference a variable defined in the Background
    Examples:
    | kittens |

The great thing about this approach is that you can set-up the JSON array using the Background section. Any Karate expression can be used in the "cell expression", and you can even use Java-interop to use external data-sources such as a database. Note that Karate has built-in support for CSV files and here is an example: dynamic-csv.feature.

JSON Function Data Source

An advanced option is where the "scenario expression" returns a JavaScript "generator" function. This is a very powerful way to generate test-data without having to load a large number of data rows into memory. The function has to return a JSON object. To signal the end of the data, just return null. The function argument is the row-index, so you can easily determine when to stop the generation of data. Here is an example:

Feature: scenario outline using a dynamic generator function

Background:
    * def generator = function(i){ if (i == 20) return null; return { name: 'cat' + i, age: i } }

Scenario Outline: cat name: <name>
    Given url demoBaseUrl
    And path 'cats'
    And request { name: '#(name)', age: '#(age)' }
    When method post
    Then status 200
    And match response == { id: '#number', name: '#(name)' }

    Examples:
    | generator |

Download details:

Author: karatelabs
Source code: https://github.com/karatelabs/karate
License: MIT license

#java #testing

Brook  Legros

Brook Legros

1659199883

String Pattern: Generate Strings Supplying A Simple Pattern in Ruby

StringPattern

With this gem, you can easily generate strings supplying a very simple pattern. Even generate random words in English or Spanish. Also, you can validate if a text fulfills a specific pattern or even generate a string following a pattern and returning the wrong length, value... for testing your applications. Perfect to be used in test data factories.

Also you can use regular expressions (Regexp) to generate strings: /[a-z0-9]{2,5}\w+/.gen

To do even more take a look at nice_hash gem

Installation

Add this line to your application's Gemfile:

gem 'string_pattern'

And then execute:

$ bundle

Or install it yourself as:

$ gem install string_pattern

Usage

What is a string pattern?

A pattern is a string where we supply these elements "a-b:c" where a is min_length, b is max_length (optional) and c is a set of symbol_type

min_length: minimum length of the string

max_length (optional): maximum length of the string. If not provided, the result will be with the min_length provided

symbol_type: The type of the string we want.
    x: from a to z (lowercase)
    X: A to Z (capital letters)
    L: A to Z and a to z
    T: National characters defined on StringPattern.national_chars
    n or N: for numbers. 0 to 9
    $: special characters, $%&#...  (includes blank space)
    _: blank space
    *: all characters
    0: empty string will be accepted.  It needs to be at the beginning of the symbol_type string
        @: It will generate a valid email following the official algorithm. It cannot be used with other symbol_type
        W: for English words, capital and lower. It cannot be used with other symbol_type
        w: for English words only lower and words separated by underscore. It cannot be used with other symbol_type
        P: for Spanish words, capital and lower. It cannot be used with other symbol_type
        p: for Spanish words only lower and words separated by underscore. It cannot be used with other symbol_type
    

How to generate a string following a pattern

To generate a string following a pattern you can do it using directly the StringPattern class or the generate method in the class, be aware you can always use also the alias method: gen

require 'string_pattern'

#StringPattern class
p StringPattern.generate "10:N"
#>3448910834
p StringPattern.gen "5:X"
#>JDDDK

#String class
p "4:Nx".gen
#>xaa3

#Symbol class
p :"10:T".generate
#>AccBdjklñD

#Array class
p [:"3:N", "fixed", :"3:N"].gen
#>334fixed920
p "(,3:N,) ,3:N,-,2:N,-,2:N".split(',').generate 
#>(937) 980-65-05

#Kernel
p gen "3:N"
#>443

Generating unique strings

If you want to generate for example 1000 strings and be sure all those strings are different you can use:

StringPattern.dont_repeat = true #default: false
1000.times {
    puts :"6-20:L/N/".gen
}
StringPattern.cache_values = Hash.new() #to clean the generated values from memory

Using dont_repeat all the generated string during the current run will be unique.

In case you just want one particular string to be unique but not the rest then add to the pattern just in the end the symbol: &

The pattern needs to be a symbol object.

1000.times {
    puts :"6-20:L/N/&".gen #will be unique
    puts :"10:N".gen
}

Generate words randomly in English or Spanish

To generate a string of the length you want that will include only real words, use the symbol types:

  • W: generates English words following CamelCase ('ExampleOutput')
  • w: generates English words following snake_case ('example_output')
  • P: generates Spanish words following CamelCase ('EjemploSalida')
  • p: generates Spanish words following snake_case ('ejemplo_salida')
require 'string_pattern'

puts '10-30:W'.gen
#> FirstLieutenant
puts '10-30:w'.gen
#> paris_university
puts '10-30:P'.gen
#> SillaMetalizada
puts '10-30:p'.gen
#> despacho_grande

If you want to use a different word separator than "_" when using 'w' or 'p':

# blank space for example
require 'string_pattern'

StringPattern.word_separator = ' '

puts '10-30:w'.gen
#> paris university
puts '10-30:p'.gen
#> despacho grande

The word list is loaded on the first request to generate words, after that the speed to generate words increases amazingly. 85000 English words and 250000 Spanish words. The vocabularies are a sample of public open sources.

Generate strings using Regular Expressions (Regexp)

Take in consideration this feature is not supporting all possibilities for Regular expressions but it is fully functional. If you find any bug or limitation please add it to issues: https://github.com/MarioRuiz/string_pattern/issues

In case you want to change the default maximum for repetitions when using * or +: StringPattern.default_infinite = 30 . By default is 10.

If you want to translate a regular expression into an StringPattern use the method we added to Regexp class: to_sp

Examples:

/[a-z0-9]{2-5}\w+/.to_sp
#> ["2-5:nx", "1-10:Ln_"]

#regular expression for UUID v4
/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/.to_sp
#> ["8:n[ABCDEF]", "-", "4:n[ABCDEF]", "-4", "3:n[ABCDEF]", "-", "1:[89AB]", "3:n[ABCDEF]", "-", "12:n[ABCDEF]"]

If you want to generate a random string following the regular expression, you can do it like a normal string pattern:


regexp = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/

# using StringPattern class
puts StringPattern.generate(regexp)

# using Kernel
puts generate(regexp)

# using generate method added to Regexp class
puts regexp.generate

#using the alias 'gen'
puts regexp.gen 

# output:
#>7009574B-6F2F-436E-BB7A-EA5FDA6B4E47
#>5FB1718F-108A-4F62-8170-33C43FD86B1D
#>05745B6F-93BA-475F-8118-DD56E5EAC4D1
#>2D6FC189-8D50-45A8-B182-780193838502

String patterns

How to generate one or another string

In case you need to specify that the string is generated selecting one or another fixed string or pattern, you can do it by using Array of patterns and in the position you want you can add an array with the possible values

p ["uno:", :"5:N", ['.red','.green', :'3:L'] ].gen

# first position a fixed string: "uno:"
# second position 5 random numbers
# third position one of these values: '.red', '.green' or 3 letters

# example output: 
# 'uno:34322.red'
# 'uno:44432.green'
# 'uno:34322.red'
# 'uno:28795xAB'

Take in consideration that this is only available to generate successful strings but not for validation

Custom characters

Also, it's possible to provide the characters we want. To do that we'll use the symbol_type [characters]

If we want to add the character ] we have to write ]]

Examples

# four chars from the ones provided: asDF9
p "4:[asDF9]".gen    #> aaaa, asFF, 9sFD

# from 2 to 20 chars, capital and lower chars (Xx) and also valid the characters $#6
p "2-20:[$#6]Xx".gen    #> aaaa, asFF, 66, B$DkKL#9aDD
 
# four chars from these: asDF]9
p "4:[asDF]]9]".gen    #> aa]a, asFF, 9s]D

Required characters or symbol types

We'll use the symbol / to specify which characters or symbols we want to be included on the resulting string as required values /symbols or characters/

If we need to add the character / we'll use //

Examples:

# four characters. optional: capitals and numbers, required: lower
"4:XN/x/".gen    # aaaa, FF9b, j4em, asdf, ADFt

# from 6 to 15 chars. optional: numbers, capitals and the chars $ and Æ. required the chars: 23abCD
"6-15:[/23abCD/$Æ]NX".gen    # bCa$D32, 32DJIOKLaCb, b23aD568C
 
# from 4 to 9 chars. optional: numbers and capitals. required: lowers and the characters $ and 5
"4-9:[/$5/]XN/x/".generate    # aa5$, F5$F9b, j$4em5, a5sdf$, $ADFt5 

Excluded characters

If we want to exclude a few characters in the result, we'll use the symbol %characters%

If you need to exclude the character %, you should use %%

Examples:

# from 2 to 20 characters. optional: Numbers and characters A, B and C. excluded: the characters 8 and 3
"2-20:[%83%ABC]N".gen    # B49, 22900, 9CAB, 22, 11CB6270C26C4572A50C

# 10 chars. optional: Letters (capital and lower). required: numbers. excluded: the characters 0 and WXYzZ
"10:L/n/[%0WXYzZ%]".gen    # GoO2ukCt4l, Q1Je2remFL, qPg1T92T2H, 4445556781

Not fulfilling a pattern

If we want our resulting string doesn't fulfill the pattern we supply, then we'll use the symbol ! at the beginning

Examples:

"!4:XN/x/".gen    # a$aaa, FF9B, j4DDDem, as, 2345

"!10:N".gen     # 123, 34899Add34, 3434234234234008, AAFj#kd2x

Generate a string with specific expected errors

Usually, for testing purposes you need to generate strings that don't fulfill a specific pattern, then you can supply as a parameter expected_errors (alias: errors)

The possible values you can specify is one or more of these ones: :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed

:length: wrong length, minimum or maximum
:min_length: wrong minimum length
:max_length: wrong maximum length
:value: wrong resultant value
:required_data: the output string won't include all necessary required data. It works only if required data supplied on the pattern.
:excluded_data: the resultant string will include one or more characters that should be excluded. It works only if excluded data supplied on the pattern.
:string_set_not_allowed: it will include one or more characters that are not supposed to be on the string.

Examples:

"10-20:N".gen errors: [:min_length]
#> 627, 098262, 3408

"20:N".gen errors: [:length, :value]
#> |13, tS1b)r-1)<RT65202eTo6bV0g~, 021400323<2ahL0NP86a698063*56076

"10:L/n/".gen errors: [:value]
#> 1hwIw;v{KQ, mpk*l]!7:!, wocipgZt8@

Validate if a string is following a pattern

If you need to validate if a specific text is fulfilling the pattern you can use the validate method.

If a string pattern supplied and no other parameters supplied the output will be an array with the errors detected.

Possible output values, empty array (validation without errors detected) or one or more of: :min_length, :max_length, :length, :value, :string_set_not_allowed, :required_data, :excluded_data

In case an array of patterns supplied it will return only true or false

Examples:

#StringPattern class
StringPattern.validate((text: "This text will be validated", pattern: :"10-20:Xn")
#> [:max_length, :length, :value, :string_set_not_allowed]

#String class
"10:N".validate "333444"
#> [:min_length, :length]

#Symbol class
:"10:N".validate("333444")
#> [:min_length, :length]

#Array class
["5:L","3:xn","4-10:n"].validate "DjkljFFc343444390"
#> false

If we want to validate a string with a pattern and we are expecting to get specific errors, you can supply the parameter expected_errors (alias: errors) or not_expected_errors (aliases: non_expected_errors, not_errors).

In this case, the validate method will return true or false.

Examples:

"10:N".val "3445", errors: [:min_length]
#> true

"10:N/[09]/".validate "4434039440", errors: [:value]
#> false

"10-12:XN/x/".validate "FDDDDDAA343434", errors: [:max_length, :required_data]
#> true

Configure

SP_ADD_TO_RUBY

This gem adds the methods generate (alias: gen) and validate (alias: val) to the Ruby classes: String, Array, and Symbol.

Also adds the method generate (alias: gen) to Kernel. By default (true) it is always added.

In case you don't want to be added, just before requiring the library set:

SP_ADD_TO_RUBY = false
require 'string_pattern'

In case it is set to true (default) then you will be able to use:

require 'string_pattern'

#String object
"20-30:@".gen 
#>dkj34MljjJD-df@jfdluul.dfu

"10:L/N/[/-./%d%]".validate("12ds6f--.s") 
#>[:value, :string_set_not_allowed]

"20-40:@".validate(my_email)

#Kernel
gen "10:N"
#>3433409877

#Array object
"(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate 
#>(937) 980-65-05

%w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen 
#>(045) 448-63-09

["1:L", "5-10:LN", "-", "3:N"].gen 
#>zqWihV-746

national_chars

To specify which national characters will be used when using the symbol type: T, you use StringPattern.national_chars, by default is the English alphabet

StringPattern.national_chars = (('a'..'z').to_a + ('A'..'Z').to_a).join + "áéíóúÁÉÍÓÚüÜñÑ"
"10-20:Tn".gen #>AAñ34Ef99éNOP

optimistic

If true it will check on the strings of the array positions supplied if they have the pattern format and assume in that case that is a pattern. If not it will assume the patterns on the array will be supplied as symbols. By default is set to true.

StringPattern.optimistic = false
["5:X","fixedtext", "3:N"].generate
#>5:Xfixedtext3:N
[:"5:X","fixedtext", :"3:N"].generate
#>AUJKJfixedtext454

StringPattern.optimistic = true
["5:X","fixedtext", "3:N"].generate
#>KKDMEfixedtext344
[:"5:X","fixedtext", :"3:N"].generate
#>SAAERfixedtext988

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/marioruiz/string_pattern.

License

The gem is available as open source under the terms of the MIT License.


Author: MarioRuiz
Source code: https://github.com/MarioRuiz/string_pattern
License: MIT license

#ruby  #ruby-on-rails