A port of kotlin-stdlib for Dart/Flutter including immutable collections (KtList, KtMap, KtSet) and other packages

Last update: May 25, 2022

kt.dart

Pub codecov

This project is a port of Kotlin's Kotlin Standard library for Dart/Flutter projects. It's a useful addition to dart:core and includes collections (KtList, KtMap, KtSet) as well as other packages which can improve every Dart/Flutter app.

dependencies: 
  kt_dart: ^0.10.0
import 'package:kt_dart/kt.dart';

Motivation

Dart's dart:core package provides basic building blocks. But sometimes they are too low level and not as straightforward as Kotlin's kotlin-stdlib.

Here are a few examples of what this project offers: (click to expand)

Immutable collections by default

dart:core collections

Dart's List is mutable by default. The immutable List.unmodifiable is the same type, but the mutation methods throw at runtime.

final dartList = [1, 2, 3];
dartList.add(4); // mutation is by default possible
assert(dartList.length == 4);

final immutableDartList = List.unmodifiable(dartList);
immutableDartList.add(5); // throws: Unsupported operation: Cannot add to an unmodifiable list

Dart's mutable List is indistinguishable from an immutable List which might cause errors.

void addDevice(List<Widget> widgets, Device device) {
  // no way to check whether widgets is mutable or not
  // add might or might now throw
  widgets.add(_deviceRow());
  widgets.add(Divider(height: 1.0));
}

kt_dart collections

KtList and KtMutableList are two different Types. KtList is immutable by default and has no mutation methods (such as add). Methods like map((T)->R) or plusElement(T) return a new KtList leaving the old one unmodified.

final ktList = listOf(1, 2, 3);
// The method 'add' isn't defined for the class 'KtList<int>'.
ktList.add(4); // compilation error
       ^^^

// Adding an item returns a new KtList
final mutatedList = ktList.plusElement(4);
assert(ktList.size == 3);
assert(mutatedList.size == 4);

KtMutableList offers mutation methods where the content of that collection can be actually mutated. I.e. with remove(T) or add(T);

// KtMutableList allow mutation
final mutableKtList = mutableListOf(1, 2, 3);
mutableKtList.add(4); // works!
assert(mutableKtList.size == 4);

All collection types have mutable counterparts:

Immutable Mutable
KtList KtMutableList
KtSet KtMutableSet, KtHashSet, KtLinkedSet
KtMap KtMutableMap, KtHashMap, KtLinkedMap
KtCollection KtMutableCollection and all the above
KtIterable KtMutableIterable and all the above
Deep equals

dart:core collections

Dart's List works like a Array in Java. Equals doesn't compare the items; it only checks the identity. To compare the contents you have to use helper methods methods from 'package:collection/collection.dart'.

// Comparing two Dart Lists works only by identity
final a = [1, 2, 3, 4];
final b = [1, 2, 3, 4];
print(a == b); // false, huh?

// Content-based comparisons require unnecessary glue code
Function listEq = const ListEquality().equals;
print(listEq(a, b)); // true

// MapEquality isn't deep by default
final x = {1: ["a", "b", "c"], 2: ["xx", "yy", "zz"]};
final y = {1: ["a", "b", "c"], 2: ["xx", "yy", "zz"]};
Function mapEq = const MapEquality().equals;
print(mapEq(x, y)); // false, wtf?!

Function deepEq = const DeepCollectionEquality().equals;
print(deepEq(x, y)); // true, finally

kt_dart collections

KtList and all other collection types implement equals by deeply comparing all items.

final a = listOf(1, 2, 3, 4);
final b = listOf(1, 2, 3, 4);
print(a == b); // true, as expected

final x = mapFrom({1: listOf("a", "b", "c"), 2: listOf("xx", "yy", "zz")});
final y = mapFrom({1: listOf("a", "b", "c"), 2: listOf("xx", "yy", "zz")});
print(x == y); // deep equals by default
Common methods

Some of Dart's method names feel unfamiliar. That's because modern languages and frameworks (Kotlin, Swift, TypeScript, ReactiveExtensions) kind of agreed on naming methods when it comes to collections. This makes it easy to switch platforms and discuss implementations with coworkers working with a different language.

expand -> flatMap

final dList = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
final kList = listOf(listOf(1, 2, 3), listOf(4, 5, 6), listOf(7, 8, 9));

// dart:core
final dFlat = dList.expand((l) => l).toList();
print(dFlat); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// kt_dart
final kFlat = kList.flatMap((l) => l);
print(kFlat); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

where -> filter

final dNames = ["Chet", "Tor", "Romain", "Jake", "Dianne"];
final kNames = listFrom(dNames);

// dart:core
final dShortNames = dNames.where((name) => name.length <= 4).toList();
print(dShortNames); // [Chet, Tor, Jake]

// kt_dart
final kShortNames = kNames.filter((name) => name.length <= 4);
print(kShortNames); // [Chet, Tor, Jake]

firstWhere -> first, firstOrNull

final dNames = ["Chet", "Tor", "Romain", "Jake", "Dianne"];
final kNames = listFrom(dNames);

// dart:core
dNames.firstWhere((name) => name.contains("k")); // Jake
dNames.firstWhere((name) => name.contains("x"), orElse: () => null); // null
dNames.firstWhere((name) => name.contains("x"), orElse: () => "Nobody"); // Nobody

// kt_dart
kNames.first((name) => name.contains("k")); // Jake
kNames.firstOrNull((name) => name.contains("x")); // null
kNames.firstOrNull((name) => name.contains("x")) ?? "Nobody"; // Nobody

KtList

KtList is a read-only list of elements. It is immutable because it doesn't offer mutation methods such as remove or add. Use KtMutableMap if you want to use a mutable list.

To create a KtList/KtMutableList use the KtList.of constructor or convert an existing Dart List to a KtList with the list.toImmutableList() extension.

Create a KtList

// Create a KtList from scratch
final beatles = KtList.of("John", "Paul", "George", "Ringo");

// Convert a existing List to KtList
final abba = ["Agnetha", "Björn", "Benny", "Anni-Frid"];
final immutableAbba = abba.toImmutableList();

Create a KtMutableList

KtList is immutable by default, which means it doesn't offer methods like add or remove. To create mutable list with kt_dart use the KtMutableList constructor.

// Create a KtMutableList from scratch
final beatles = KtMutableList.of("John", "Paul", "George", "Ringo");
beatles.removeAt(0);
print(beatles); // [Paul, George, Ringo]

Mutable/Immutable conversion

Conversions between KtList and KtMutableList can be done with KtList.toMutableList() and KtMutableList.toList();

final beatles = KtList.of("John", "Paul", "George", "Ringo");
final mutable = beatles.toMutableList();
mutable.removeAt(0);
print(mutable); // [Paul, George, Ringo]
print(beatles); // [John, Paul, George, Ringo]

for loop

kt_dart collections do not implement Iterable. It is therefore not possible to directly iterate over the entries of a KtList.

All kt_dart collections offer a .iter property which exposes a Dart Iterable. For-loops therefore don't look much different.

final beatles = KtList.of("John", "Paul", "George", "Ringo");
for (final member in beatles.iter) {
  print(member);
}

Yes, alternatively you could use .asList() instead which returns a Dart List.

Kotlin syntax

Kotlin users might be more familiar with the listOf() and mutableListOf() functions. Use them if you like but keep in mind that the dart community is much more used to use constructors instead of top-level functions.

final beatles = listOf("John", "Paul", "George", "Ringo");
final abba = mutableListOf("Agnetha", "Björn", "Benny", "Anni-Frid");

KtSet

A KtSet is a unordered collection of elements without duplicates.

Creating a KtSet/KtMutableSet is very similar to the KtList API.

// Create a KtSet from scratch
final beatles = KtSet.of("John", "Paul", "George", "Ringo");

// Convert a existing Set to KtSet
final abba = {"Agnetha", "Björn", "Benny", "Anni-Frid"};
final immutableAbba = abba.toImmutableSet();

KtMap

To create a KtMap/KtMutableMap start with Dart Map and then convert it to a KtMap with either:

  • pokemon.toImmutableMap(): KtMap (since Dart 2.7)
  • KtMap.from(pokemon): KtMap
  • pokemon.kt: KtMutableMap (since Dart 2.7)
  • KtMutableMap.from(pokemon): KtMutableMap
// immutable
final pokemon = {
  1: "Bulbasaur",
  2: "Ivysaur",
  3: "Stegosaur",
}.toImmutableMap();

final newPokemon = KtMap.from({
  152: "Chikorita",
  153: "Bayleef",
  154: "Meganium",
});

// mutable
final mutablePokemon = {
  1: "Bulbasaur",
  2: "Ivysaur",
  3: "Stegosaur",
}.kt;

final newMutablePokemon = KtMutableMap.from({
  152: "Chikorita",
  153: "Bayleef",
  154: "Meganium",
});

KtHashMap and KtLinkedMap

You may want to use a specific Map implementation. kt_dart offers:

  • KtLinkedMap - based on Darts LinkedHashMap where the insertion order of keys is remembered and keys are iterated in the order they were inserted into the map
  • KtHashMap - based on Darts HashMap where keys of a HashMap must have consistent [Object.==] and [Object.hashCode] implementations. Iterating the map's keys, values or entries (through [forEach]) may happen in any order.

KtPair, KtTriple

kt_dart offer two types of tuples, KtPair with two elements and KtTriple with three elements. They are used by some collection APIs and prevent a 3rd party dependency.

final beatles = KtList.of("John", "Paul", "George", "Ringo");
final partitions = beatles.partition((it) => it.contains("n"));
print(partitions.first); // [John, Ringo]
print(partitions.second); // [Paul, George]

There won't be a KtQuadruple or TupleN in this library. If you want to use tuples heavily in you application consider using the tuple package. Better, use freezed to generated data classes which makes for a much better API.

Annotations

@nullable

Kotlin already has Non-Nullable types, something which is coming to Dart soon™. kt_dart already makes use of Non-Nullable Types and never returns null unless a method is annotated with @nullable.

/// Returns the value corresponding to the given [key], or `null` if such a key is not present in the map.
@nullable
V get(K key);

There isn't any tooling which will warn you about the wrong usage but at least it's documented. And once nnbd lands in Dart it will be fairly easy to convert.

@nonNull

This annotation annotates methods which never return null. Although this is the default in kt_dart, is makes it very obvious for methods which sometimes return null in other languages.

@experimental

A method/class annotated with @experimental marks the method/class as experimental feature. Experimental APIs can be changed or removed at any time.

License

Copyright 2019 Pascal Welsch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

GitHub

https://github.com/passsy/kt.dart
Comments
  • 1. groupByTransform(_).getOrDefault(_, KtList.empty()) crashes always

    As groupByTransform directly casts KtMutableMap,KtMutableList to KtMap,KtList. when getOrDefault(key, KtList.empty()) is used on returned map from groupByTransform. It crashes with error type 'EmptyList<Currency>' is not a subtype of type 'KtMutableList<Currency>' of 'defaultValue'

    Reproducible code

    main() {
      var usd = Currency(1, "USD");
      var inr = Currency(2, "INR");
      KtList<Currency> currency = listOf(usd, inr);
      KtList<CurrencyConversionData> conversion =
          listOf(CurrencyConversionData(1, 2));
      KtMap<Currency, KtList<Currency>> conversions =
          _toCurrencyMap(conversion, currency);
    
      print(conversions.getOrDefault(Currency(1, "USD"), KtList.empty())); // CRASH HERE (type 'EmptyList<Currency>' is not a subtype of type 'KtMutableList<Currency>' of 'defaultValue')
    }
    
    KtMap<Currency, KtList<Currency>> _toCurrencyMap(
        KtList<CurrencyConversionData> conversion, KtList<Currency> currency) {
      KtMap<int, Currency> currencyMap = currency.associateBy((cur) => cur.id);
      return conversion.groupByTransform(
        (conv) => currencyMap.get(conv.fromRef)!,
        (conv) => currencyMap.get(conv.toRef)!,
      );
    }
    
    class Currency {
      final int id;
      final String ticker;
    
      Currency(this.id, this.ticker);
    }
    
    class CurrencyConversionData {
      final int fromRef;
      final int toRef;
    
      CurrencyConversionData(this.fromRef, this.toRef);
    }
    
    
    Reviewed by Ashok-Varma at 2021-05-20 14:30
  • 2. KtMap.map(MapEntry -> R) : KtList

    Kotlin allows mapping of entries in a map using .map

    mapOf(1 to "foo", 2 to "bar")
            .map { "${it.key} -> ${it.value}" }
            .forEach(::print)
    // 1 -> foo
    // 2 -> bar
    

    This leads to a name clash with KtMap.map which returns the Dart Map.

    Currently all kt.dart collections allow access to the dart equivalents.

    KtIterable.iter -> Iterable
    KtList.list -> List
    KtMap.map -> Map
    KtSet.set -> Set
    

    We have to find a way to provide standardized ways to access the dart equivalents. Additionally we should make it as easy as possible to make kt.dart collections useable using for loops.

    // kt.dart 0.5.0
    for(final i in listOf(1, 2, 3).iter) {
      print(i);
    }
    
    Reviewed by passsy at 2019-02-20 14:40
  • 3. Migration to Extensions (Dart 2.6)

    Dart 2.6 introduces extension methods. The aim of this project was always to provide tons of extensions.

    This PR enables migrates the old extension mixins to real extensions. Nothing else. No further APIs or internal extension usage

    Reviewed by passsy at 2019-10-08 01:10
  • 4. Shuffle

    I develop an app that involves massive working with lists. One of the popular features that I use is list shuffling.

    As I see there is no such a method in this library yet. It is very frustrating to switch to standard dart collections to just shuffle and then go back to kt.dart which is awesome btw.

    Do you have plans to implement this functionality?

    Reviewed by votruk at 2019-04-13 09:13
  • 5. Question about KtList

    In the docs there's this:

    kt_dart collections do not implement Iterable. It is therefore not possible to directly iterate over the entries of a KtList.

    But there package also exposes a iterator via .iter property as shown in the example. So here comes my question, is there a reason for kt.dart not to implement Iteratable?

    Reviewed by Ascenio at 2021-05-25 11:52
  • 6. Introduce minOf() on KtIterable

    Closes #157 by adding minOf() method on KtIterable.

    Kotlin does not allow to omit the selector function (see example)

    If wanted other functions could be checked too to let it be omitted if possible.

    Reviewed by robiness at 2021-10-14 13:16
  • 7. Use dart 2.14 to reformat

    This tries to fix a format issue i ran into with #145 and of future PRs with the analysis workflow failing because of different cascade formatting when using dart 2.12.

    I am also wondering, if this empty line on eof is okay or why it is missing 🤔.

    Reviewed by robiness at 2021-10-07 16:55
  • 8. filterNotNull() doesn't change type to non nullable list

    I have the following scenario.

    Actual: I get a KtList<T?> objects I run the following code final KtList<T> nonNullObjects = objects.filterNotNull();

    The dart type check still requires me to use a nullable T? type instead of a non-nullable one

    Expect: filterNotNull() also changes the returned type to T and doesn't require me to continue working with T?

    Reviewed by luckyhandler at 2021-07-20 13:06
  • 9. Performance Improvement to KtSet.contains

    Hello there from a related project called Fast Immutable Collections (FIC). In it, we created a benchmark_app to compare our collections to the main ones and other notable packages — currently kt.dart and built_collection. The project is not finished yet and neither is the benchmark app itself, so please don't take what you find there as our final say.

    Anyway, we stumbled upon a peculiar result when running contains benchmarks for sets. All of the collections were giving similar results with the exception of KtDart's:

    WithIteratorMixin

    After a little bit of digging I found out that KtDart uses IterableMixin inside its UnmodifiableSetView. This lets the view implement the Set interface with way less work. However, it uses Iterable implementations of methods and not necessarily what you would like specifically for Sets. A contains for a Set and a contains for an Iterable would have require different implementations optimizations. For example, the contains for IterableMixin has an internal loop which slows down UnmodifiableSetView:

    bool contains(Object? element) {
      for (E e in this) {
        if (e == element) return true;
      }
      return false;
    }
    

    In the end, fixing this is apparently not that difficult. What I did was simply override contains to use the _set's contains instead:

    @override
    bool contains(Object? element) => _set.contains(element);
    

    This is the result after adding that bit of code:

    PatchedContains

    That said, I think it would be even easier and less code to use SetMixin instead, which is also what we ended up using in FIC. But I don't know the other requirements of the project, so I'll leave it for others to fill in the gaps.

    Reviewed by psygo at 2021-01-05 17:52
  • 10. Standard extensions

    Add the standard kotlin extensions

    • T.let(block: (T) -> R): R
    • T.also(block: (T) -> Unit): T
    • T.takeIf(predicate: (T) -> bool): T?
    • T.takeUnless(predicate: (T) -> bool): T?

    and top-level functions

    • TODO() -> Always throws [NotImplementedException] stating that operation is not implemented.
    • void repeat(int times, void Function(int) action) Executes the given function [action] specified number of [times].

    no run { }?

    I explicitly did not add run because

    1. I'm very careful with adding top-level functions which might conflict with other packages. run is pretty common and could lead to confusion.
    2. Dart supports this construct already
    // how run would work
    final result = calculation() ?? run(() => 42);
    final result = calculation() ?? 
      run(() { 
       return 42
      });
    
    // in pure dart
    final result = calculation() ?? (() => 42)();
    final result = calculation() ?? 
      () {
        return 42;
      })();
    
    Reviewed by passsy at 2020-03-01 18:57
  • 11. Mutable collection classes should not implement their immutable counterparts

    Example of why I think this is bad behavior:

    final mutableList = mutableListOf(1, 2, 3);
    final KtList<int> immutableList = mutableList;
    mutableList[0] = 0;
    print(immutableList); // Prints [0, 2, 3]!
    

    I think this breaks the expectation that immutable collections should never change, which might cause some bugs.

    I'd be happy to open a PR, although I imagine such a large breaking change would require a new major version.

    Reviewed by mhmdanas at 2020-02-28 17:38
  • 12. KtList (and others) has no const factories

    KtList (and others) has no const factories with const initializers. For example, KtList.of(1, 2, 3). And therefore no available pass constructed list with const to const constructors:

    class State {
        final KtList<int> keys;
        const State(this.keys);
    }
    ...
    
    final state = const State(KtList.of(1, 2, 3, 4)); //no compile
    
    
    Reviewed by myvaheed at 2020-12-21 17:58
  • 13. KMutableEntry.setValue doesn't change value in collection

    KMutableEntry.setValue doesn't change the underlying collection. Here is a valid test which is currently failing

    final pokemon = mutableMapOf({
      1: "Bulbasaur",
      2: "Ivysaur",
    });
    
    pokemon.entries.forEach((entry) {
      entry.setValue(entry.value.toUpperCase());
    });
    
    // throws because pokemon is unchanged
    expect(
        pokemon,
        mapOf({
          1: "BULBASAUR",
          2: "IVYSAUR",
        }));
    
    Reviewed by passsy at 2019-01-03 15:27
  • 14. KMutableIterator.remove() doesn't remove item from collection

    var abc = mutableListOf(["a", "b", "c"]);
    final KMutableIterator<String> i = abc.iterator();
    assert(i.next() == "a");
    i.remove();
    assert(abc[0] == "b"); // Failed assertion: 'abc[0] == "b"': is not true.
    

    abc[0] is still "a" because remove doesn't remove the item from the actual collection. Instead, it gets removed from the internal copy inside the iterator.

    To fix this problem a big refactoring is required so that remove actually removed the item from the original collection.

    This is how kotlin solves this

    Reviewed by passsy at 2018-12-02 16:30

Related

Functional extensions to Dart collections.

collection_ext A set of extension methods for Dart collections, designed for the purpose of making it easier to write concise, functional-programming-

Dec 28, 2021
Immutable value types, enum classes, and serialization.

Built Values for Dart Introduction Built Value provides: Immutable value types; EnumClass, classes that behave like enums; JSON serialization. Immutab

May 19, 2022
Github Trending app built with Flutter+Redux+Built(Immutable Data)

Github Trending app built with Flutter+Redux+Built(Immutable Data)

May 13, 2020
A pure Dart utility library that checks for an internet connection by opening a socket to a list of specified addresses, each with individual port and timeout. Defaults are provided for convenience.

data_connection_checker A pure Dart utility library that checks for an internet connection by opening a socket to a list of specified addresses, each

Mar 24, 2022
Dart port of FormCoreJS: A minimal pure functional language based on self dependent types.

FormCore.js port to Dart. So far only the parser and typechecker have been ported i.e. the FormCore.js file in the original repo. (Original readme fro

Jan 28, 2022
Radiance - Dart port of gdx-ai

⚠️ This package is currently under development. This library is a collection of AI algorithms used in games. This includes: Steering behaviors (NYI) F

Apr 3, 2022
Klutter plugin makes it possible to write a Flutter plugin for both Android and iOS using Kotlin only.

The Klutter Framework makes it possible to write a Flutter plugin for both Android and iOS using Kotlin Multiplatform. Instead of writing platform spe

May 22, 2022
Flutterkotlinlottie - A sample flutter app illustrating the implementation of a lottie splash screen natively through kotlin
Flutterkotlinlottie - A sample flutter app illustrating the implementation of a lottie splash screen natively through kotlin

flutter_lottie_splash_app A Flutter application to demonstrate how to add an ani

Jan 1, 2022
GitHub Action that uses the Dart Package Analyzer to compute the Pub score of Dart/Flutter packages
GitHub Action that uses the Dart Package Analyzer to compute the Pub score of Dart/Flutter packages

Dart/Flutter package analyzer This action uses the pana (Package ANAlysis) package to compute the score that your Dart or Flutter package will have on

May 17, 2022
📅 Customizable flutter calendar widget including day and week views

?? Customizable, animated calendar widget including day, week, and month views. Navigation Animation Callbacks Changing the VisibleDateRange Available

May 25, 2022
Flutter mobile app with firestore authentication including Email and Social auth.
Flutter mobile app with firestore authentication including Email and Social auth.

Flutter mobile app with firestore authentication including Email and Social auth.

May 18, 2022
CARP Mobile Sensing for Flutter, including mobile sensing framework, data backend support, and the CARP mobile sensing app.
CARP Mobile Sensing for Flutter, including mobile sensing framework, data backend support, and the CARP mobile sensing app.

This repo hold the source code for the CACHET Research Platform (CARP) Mobile Sensing (CAMS) Flutter software. It contains the source code for CACHET

May 25, 2022
A iOS like table view including section, row, section header and divider
A iOS like table view including section, row, section header and divider

flutter_section_table_view A iOS like table view including section, row, section header and divider Support both Android and iOS Support drop-down ref

Dec 24, 2021
An app to show everything bus related in Singapore, including arrival times and a directory
An app to show everything bus related in Singapore, including arrival times and a directory

NextBus SG An app to show everything bus related in Singapore, including bus arrival times and a directory, with extra features. ?? Gallery Click here

May 12, 2022
Boilerplate codes including Superbase settings for Flutter
Boilerplate codes including Superbase settings for Flutter

flutter_boilerplate_supabase Boilerplate codes including Superbase settings for Flutter. Getting Started You have to create the .env file. Rename the

Feb 7, 2022
Building a simple Flutter app for understanding the BLoC State Management including: Cubit, Managing Route & showSnackBar.

Building a simple Flutter app for understanding the BLoC State Management including: Cubit, Managing Route & showSnackBar.

Apr 25, 2022
Dart and Flutter sealed class generator and annotations, with match methods and other utilities. There is also super_enum compatible API.

Dart Sealed Class Generator Generate sealed class hierarchy for Dart and Flutter. Features Generate sealed class with abstract super type and data sub

Jan 17, 2022