NvTooling

An assortment of useful classes used for development at NonVanilla.

SuccessOrFailure

SuccessOrFailure is a way to get rid of your exceptions and force yourself to handle the error case whenever you want to access the date inside this class. 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 SuccessOrFailure. successOf and failureOf which create a SuccessOrFailure instance with either a Success or Failure object.

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

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

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

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

final SuccessOrFailure<SomeObject, FreezedErrorUnion> myObjects = await getObjectFromServer successOf(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,
    );
)

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']