states_rebuilder

repository·master·Indexed 19 days ago

https://github.com/gifatahth/states_rebuilder

A comprehensive Flutter framework integrating state management, dependency injection, and routing. It features ReactiveStatelessWidget, OnReactive, and OnBuilder for optimized rebuilds, as well as support for global and local state scopes. The library includes tools for async state management with predefined status flags (isIdle, isWaiting, hasData, hasError), state interceptors for validation, and RM.injectNavigator for Flutter Navigation 2.0 with support for guards, deep-linking, and custom transitions. Additionally, it provides InjectedCRUD for simplifying RESTful API operations.

Tokens
52.2K
Snippets
139
Records
189
Agent score
60%

What's inside states_rebuilder

  1. Overview of states_rebuilder

    master
    states_rebuilder is a Flutter solution that combines state management, dependency injection, and an integrated router. It is designed to provide a high-performance state management experience while speeding up development through features like zero-boilerplate business logic and built-in support for common app requirements.
  2. Explore authentication and authorization use-cases

    master

    The ex006_00_authentication_and_authorization example is divided into specific implementation patterns:

    1. Basic User Authentication: Uses InjectedAuth and a repository pattern. Includes frontend and backend validation for sign-in/up forms.
    2. Authentication with InjectedNavigator: A version of the authentication flow rewritten specifically to use the InjectedNavigator API.
    3. Token Refreshing and Auto-Logout: Demonstrates handling token expiration scenarios, including automatic logout and proactive token refreshing, while persisting credentials.
    4. Mocking InjectedAuth: Provides different strategies for mocking the InjectedAuth service for testing purposes.
  3. Understand the purpose of todos_app_core

    master

    The todos_app_core package serves as a central repository for shared primitives used across different implementations of the Todos app. It provides essential, cross-cutting concerns that ensure consistency between various UI or platform-specific versions of the application.

    Key shared components include:

    • Routes: Standardized navigation paths and routing logic.
    • Theme: Centralized styling and visual configuration.
    • Localizations: Shared translation and internationalization resources.
  4. Manage Global and Local (Scoped) state

    master

    States in states_rebuilder can be managed at two levels:

    Global State

    Defined in the global scope. These states are singletons and accessible anywhere.

    final myState = RM.inject(() => MyState());

    Local (Scoped) State

    Allows you to encapsulate state within a specific part of the widget tree. Each instance of the widget gets its own independent state. To achieve this, declare the state globally but use .inherited() in the widget tree to override it with a local instance.

    To read the scoped state, use .of(context) or .inherited(context).

    // 1. Declare globally with a placeholder
    final myState = RM.inject(() => throw UnimplementedError());
    
    // 2. In the widget tree, override it
    myState.inherited(
      stateOverride: () => MyState(param1, param2),
      builder: (context) {
        // 3. Read the local instance
        final localState = myState.of(context);
        return Text('${localState.state}');
      },
    );
  5. Use RMKey to synchronize state across widgets

    master

    An RMKey acts similarly to Flutter's GlobalKey, allowing you to control and synchronize StateBuilder observer widgets from outside their own builder closures.

    By assigning an RMKey to the rmKey parameter of a StateBuilder, you can update the state from any other widget in the tree (e.g., a button in a different part of the Scaffold) by modifying the RMKey.value property. Other widgets can also subscribe to the same RMKey via the observe parameter to stay synchronized.

    // 1. Define the key
    final switchKey = RMKey(true);
    
    // 2. Assign to a StateBuilder to control/notify it
    StateBuilder<bool>(
      observe: () => RM.create(true),
      rmKey: switchKey,
      builder: (ctx, switchRM) {
        return Switch(
          value: switchRM.value,
          onChanged: (value) => switchRM.value = value,
        );
      },
    )
    
    // 3. Subscribe another widget to the same key
    StateBuilder<bool>(
      observe: () => switchKey,
      builder: (ctx, switchRM) {
        return Switch(
          value: switchRM.value,
          onChanged: (value) => switchRM.value = value,
        );
      },
    )
    
    // 4. Update from anywhere
    switchKey.value = !switchKey.value;
  6. Implement optimistic and pessimistic async updates

    master

    When dealing with lists or complex data, you can choose between two primary patterns for handling asynchronous mutations:

    Pessimistic Updates

    Wait for the asynchronous operation to complete successfully before updating the UI. This is typically implemented using a state interceptor with a Future. The UI only reflects the new state once the backend/service confirms the change without error.

    Optimistic Updates

    Update the UI immediately with the expected new state to provide a snappy user experience. If the asynchronous operation fails, the state is reverted to the last known good state. This is typically implemented using a state interceptor with a Stream.

  7. Create dependent injected models

    master

    Injected models can depend on other injected models using the dependsOn parameter. When a dependency changes, the dependent model recalculates its state and notifies its own listeners. You can also use debounceDelay to prevent rapid recalculations.

    Example: Creating a helloName model that reacts to changes in a name model.

    final name = RM.inject(() => '');
    
    final helloName = RM.inject(
      () => 'Hello ${name.state}',
      dependsOn: DependsOn({
        name,
        debounceDelay: 400, // Wait 400ms after last change before recalculating
      }),
    );
  8. Implement navigation guards and redirection

    master

    The states_rebuilder API provides several ways to handle complex navigation logic:

    • Authentication Guards: Use InjectedNavigator.onNavigate to create redirection guards (e.g., redirecting to a sign-in page if auth status is invalid).
    • Pop Validation (Global): Prevent users from leaving a page without validating data by implementing a global back navigation guard.
    • Pop Validation (Local): Use the OnBackNavigationScope widget to handle data validation locally for a specific page.
    • Cyclic Redirection: The API is designed to handle cyclic redirection safely.
    • Redirection Info: When a route is redirected, the target route can access information about the route it was redirected from.
  9. Manage async state with predefined status flags

    master

    The states_rebuilder package provides predefined status flags to manage the lifecycle of asynchronous operations. Instead of manually managing custom status enums, you can use these built-in flags to track the state of your data:

    • isIdle: The initial state before any async task has started.
    • isWaiting: The state while an asynchronous task is currently in progress.
    • hasData: The state when the asynchronous task has completed successfully and data is available.
    • hasError: The state when the asynchronous task has failed.

    You can use these flags to manually mutate state or leverage the full states_rebuilder API to handle these transitions automatically, ensuring you don't lose track of what triggers a state change.

  10. Deep-linking and Route Management

    master

    For deep-linking in Nav2, the library provides different methods to control how routes are pushed:

    • InjectedNavigator.to: Standard navigation.
    • InjectedNavigator.toDeeply: Used for deep-linking scenarios where multiple layers of the navigation stack might need to be addressed.
    • RouteWidget: You can use RouteWidget to wrap your pages, which allows for decentralized route logic (e.g., using static helper methods within the widget itself) and easier deep-linking implementation.
  11. Use Rebuilder widgets for state management

    master

    Instead of standard Flutter FutureBuilder or StreamBuilder, states_rebuilder provides specialized widgets that handle various state statuses more exhaustively.

    Available Rebuilder Widgets

    • StateBuilder: The default widget listener for state changes.
    • WhenRebuilder: Exhaustively handles all available state statuses: onIdle, onWaiting, onError, and onData.
    • WhenRebuilderOr: Allows selective handling of specific state statuses.
    • OnSetStateListener: Used to execute side effects.

    Handling Futures and Streams

    When using WhenRebuilder with futures or streams, you can define the resulting type in the generic parameters. For example, WhenRebuilder<Foo>(models: [RM.future<int>(...)]) means the model is of type Foo and the resolved value is an int.

    // Using WhenRebuilder with a future
    WhenRebuilder<Foo>(
      models: [RM.future<int>(Injector.get<Foo>().login())],
      onIdle: () => Text('Welcoming Screen'),
      onWaiting: () => SplashScreen(),
      onError: (e) => Text('An Error has happened'),
      onData: (userId) => Text('${userId}'), // userId is the resolved int
    )
    
    // Using WhenRebuilder with a stream
    WhenRebuilder<int>(
        models: [RM.stream<int>(IN.get<Foo>().fireStoreStream())],
        onIdle: () => Text('Welcoming Screen'),
        onWaiting: () => SplashScreen(),
        onError: (error) => Text('An Error has happened'),
        onData: (userId) => Text('${userId}'),
    )
  12. Implement Domain Entities and Value Objects

    master

    The Domain layer uses two types of data objects:

    Entities

    Entities are mutable objects with unique IDs. They represent the in-memory state of data retrieved from a data_source. They should contain the logic they control and be validated just before persistence.

    Value Objects

    Value objects are immutable objects that have value equality and self-validation but no IDs. They are typically used for form validation (e.g., Email, Password). A value object should either be created in a valid state or throw a ValidationException during construction.

    Example: Value Object with self-validation

    class Email {
      Email(this.value) {
        if (!Validators.isValidEmail(value)) {
          throw ValidationException('Enter a valid email');
        }
      }
    
      final String value;
    }
    class User {
      final String uid;
      final String email;
      final String displayName;
      final String photoUrl;
    
      User({
        this.uid,
        this.email,
        this.displayName,
        this.photoUrl,
      });
    }