NvTooling

An assortment of useful classes used for development at NonVanilla.

Result

Result is inspired by Rusts type of the same name. It provides a more conscious way of dealing with Exceptions. Especially when making API calls it is very easy to forget to handle certain exceptions. This class won't let you do that.

There are two methods that return a Result, resultOf and exceptionOf which create a Result instance with either a Result or an Exception object.

It then also provides an access(...) method which takes two callbacks for both scenarios (Result and Exception).

The additional map(...) method allows mutating the Result class easily without having to handle both scenarios.

// The types are quite long this way so creating typedefs can make them more readable
typedef AsyncResultOrException = Future<Result<SomeObject, FreezedErrorUnion>>;

// Let's pretend we are doing some remote work here
AsyncResultOrException getObjectFromServer() async => resultOf(SomeObject());

final Result<SomeObject, FreezedErrorUnion> myObjects = await getObjectFromServer(true);

// Notice that this changes the type
final newMyObjects = myObjects.map(failure: (failure) => 'Something went wrong'); 

// And the finally inside your widget, access your data (and handle the error case).
Text(
    newMyObjects.access(
        (myObject) => myObject.toString(), 
        (failure) => failure,
    );
)

// Most importantly it adds the unwrap method which returns the data in case of a `Result` or throws the `Exception`.
final obj = newMyObjects.unwrap();

Selection/OptionalSelection

This type keeps all the options as well as the selected option in one place. If you have a list of Strings and want your user to select one it is sometimes the case, that you need to know both the selected option as well as all the available ones.

OptionalSelection allows for the selection to be null while Selection makes sure there is always one item selected.

final selection = Selection(
    options: ['Hello', 'my', 'friend'], 
    selected: 'my',
);

Iterable extensions

mapGrouped

Groups elements together according to the groupBy callback and then applies the groupBuildermethod to each individual group.

final names = ['Alex', 'Anna', 'Bill', 'Benny'];
final namesGrouped = Map.fromEntries(
    names.mapGrouped(
        groupBy: (name) => name[0], 
        groupBuilder: (key, values) => MapEntry(key, values),
    ),
);

print(namesGrouped);

// Output: 
// { 'A': ['Alex', 'Anna'], 'B': ['Bill', 'Benny'] }

mapIndexed

Gives you access to the index of the element while mapping.

final names = ['Alex', 'Anna', 'Bill', 'Benny'];
final namesWithIndex = names.mapIndexed(
    (index, element) => '$index: $element',      
);

print(namesWithIndex);

// Output: 
// ['0: Alex', '1: Anna', '2: Bill', '3: Benny']

separatedBy

separatedBy inserts the supplied element in between every two elements in the iterable.

final names = ['Alex', 'Anna', 'Bill'];
final namesWithJolo = names.separatedBy('Jolo');

print(namesWithJolo);

// Output: 
// ['Alex', 'Jolo', 'Anna', 'Jolo', 'Bill']