Skip to content

agmoss/remote_province

 
 

Repository files navigation

RemoteState

import 'package:remote_province/remote_province.dart';

A province is a political division within a country, typically used in countries that are federal or have a federal system of government. A state, on the other hand, is a political entity that is typically sovereign and has defined geographic boundaries.

pub package

Tools for mapping data from remote sources in Dart, similar to Elm's RemoteData: https://elmprogramming.com/remote-data.html

Slaying a UI Antipattern with Flutter.

Library inspired by a blog post by Kris Jenkins about How Elm slays a UI antipattern.

What problem does this package solve?

You are making an API request, and you want to display or do different things based on the status of the request.

Why RemoteState, not RemoteData?

I gained secondary inspiration from a talk by Jed Watson, A Treatise on State. As much as possible, I try to categorize state correctly in my applications.

The RemoteState approach

Instead of using a complex object we use a single data type to express all possible request states. This approach makes it impossible to create invalid states.

Usage

A common use case for RemoteState would be mapping it into a UI transition or component state. Here is an example that uses StateNotifier, found in examples/counter_state_notifier

import 'package:remote_province/remote_province.dart';

class CounterNotifier extends StateNotifier<RemoteState<int>> {
  var _counterClient = CounterClient();

  CounterNotifier() : super(RemoteState.initial()) {
    getCount();
  }

  getCount() async {
    state = RemoteState.loading();

    state = await RemoteState.guard(() => _counterClient.getCount());
  }

  increment() async {
    state = await RemoteState.guard(() => _counterClient.increment());
  }

  decrement() async {
    state = await RemoteState.guard(() => _counterClient.decrement());
  }
}
import 'package:remote_province/remote_province.dart';

class ExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StateNotifierProvider<CounterNotifier, RemoteState<int>>.value(
        value: CounterNotifier(),
        child: HomePage(),
      ),
    );
  }
}
import 'package:remote_province/remote_province.dart';

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

  @override
  Widget build(BuildContext context) {
    //2. Resolve counter notifier to update state
    var counterNotifier = Provider.of<CounterNotifier>(context);
    var counterState = Provider.of<RemoteState<int>>(context);

    var textStyle = Theme.of(context).textTheme.headline4;
    final fabPadding = EdgeInsets.symmetric(vertical: 5.0);

    return Scaffold(
      appBar: AppBar(
        title: Text('RemoteState with StateNotifier'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            //3. Render state changes
            counterState.when(
              initial: () => Text('Not loaded', style: textStyle),
              success: (value) => Text('$value', style: textStyle),
              loading: () => Text('Loading...', style: textStyle),
              error: (_) => Text('Error', style: textStyle),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: fabPadding,
            child: FloatingActionButton(
              heroTag: 'inc',
              child: Icon(Icons.add),
              //4. Perform increment action
              onPressed: () => counterNotifier.increment(),
            ),
          ),
          Padding(
            padding: fabPadding,
            child: FloatingActionButton(
              heroTag: 'dec',
              child: Icon(Icons.remove),
              //5. Perform decrement action
              onPressed: () => counterNotifier.decrement(),
            ),
          ),
        ],
      ),
    );
  }
}

maybeWhen

import 'package:remote_province/remote_province.dart';
var result = remoteState.maybeWhen(
  initial: () => "It's initial",
  loading: () => "It's loading",
  // Don’t provide handlers for success and error.
  orElse: () => "It's either success or error", // Wildcard
);

API

RemoteState

RemoteState<T> is usedto annotate your request variables. It wraps all possible request states into one single union type. Use the parameters to specify.

  • T: The success value type.

RemoteState.initial

RemoteState.initial is an instance of RemoteState that signifies the request hasn't been made yet.

RemoteState.loading

RemoteState.loading is an instance of RemoteState that signifies the request has been made, but it hasn't returned any data yet.

RemoteState.success

RemoteState.success is an instance of RemoteState that signifies the request has completed successfully and the new data (of type T) is available.

RemoteState.error

RemoteState.error is an instance of RemoteState that signifies the request has failed.

RemoteState.guard

RemoteState.guard is a static function that converts a Future to RemoteState. It will emit RemoteState.error if the future fails or RemoteState.success if the future completes.

Pattern matching high order functions

When

The when method is a high order function that accepts a method for each state and matches the request state with the appropriate callback function. All callbacks are required and must not be null.

MaybeWhen

The maybeWhen method is a high order function that accepts a method for each state and matches the request state with the appropriate callback function or a fallback callback for missing methods. Only orElse is required.

Map

The map method is the equivalent of when without the destructuring.

MaybeMap

The maybeWhen method is the equivalent of when without the destructuring.

State Predicates

isInitial

The isInitial predicate returns true if we haven't asked for data yet.

isLoading

The isLoading predicate returns true if we're loading.

isSuccess

The isSuccess predicate returns true if we've successfully loaded some data.

isError

The isError predicate returns true if we've failed to load some data.

Maintainers

References

Dev

1. Fetching Dependencies

dart pub get

2. Running Tests

dart test

3. Code Generation (freezed, json_serializable, etc.)

dart pub run build_runner build

For continuous rebuilding as you change the code:

dart pub run build_runner watch

4. Code Analysis

dart analyze

5. Formatting Code

dart format lib

6. Running the Package/Application

dart run

7. Compiling the Package/Application

dart compile exe <dart_file>

8. Publishing the Package

dart pub publish

9. Release

./scripts/prepare_release.sh

About

Tools for mapping data from remote sources in Dart, similar to Elm's RemoteData: https://elmprogramming.com/remote-data.html

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Dart 98.8%
  • Shell 1.2%