StateNotifier는 Flutter 프레임워크 외부에서 상태 관리를 위한 확장 기능을 제공하는 Dart 패키지로, ValueNotifier를 확장한 것입니다. 비동기 작업, 오류 처리 및 디버깅 도구를 내장한 깔끔하고 반응형 상태 관리 방식을 제공합니다. 웹, 데스크톱, 서버 측 애플리케이션과 같은 비-Flutter 환경에서 사용하도록 설계되었으며, 종속성 주입과 잘 통합되어 관심사의 분리(Seperation of Concerns)를 촉진합니다. 불변 업데이트와 간소화된 구독을 통해 성능과 개발자 경험을 향상시킵니다.
사용 사례
Flutter가 아닌 Dart 앱에서 애플리케이션 상태 관리하기
오류 복구를 사용한 비동기 상태 전이 처리
의존성 주입 시스템과의 통합
다양한 플랫폼 간에 재사용 가능한 상태 로직 구축
테스트 가능하고 유지보수 가능한 상태 클래스 만들기
주요 기능
추가 기능을 제공하는 ValueNotifier 확장
비동기 상태 업데이트 및 오류를 지원합니다
불변 상태 업데이트
내장된 디버깅 유틸리티
플랫폼 독립적 설계
DI 프레임워크와의 간편한 통합
적합한 대상
크로스 플랫폼 앱을 구축하는 Dart 개발자
여러 플랫폼 간에 공유 상태 로직이 필요한 팀
Flutter 없이 강력한 상태 관리가 필요한 프로젝트
경량이고 테스트 가능한 상태 솔루션을 찾는 개발자
고려 사항
Flutter 위젯 내에서 직접 사용하기 위한 것이 아님
수동 구독 관리가 필요합니다
의존성 주입 시스템과 가장 잘 어울림
제한된 내장 UI 통합
복잡한 상태 흐름을 위해 추가적인 보일러플레이트가 필요할 수 있습니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Welcome to state_notifier~
This package is a recommended solution for managing state when using Provider or Riverpod.
The purpose of StateNotifier is to be a simple solution to control state in
an immutable manner.
While ChangeNotifier is simple, through its mutable nature, it can be harder to
maintain as it grows larger.
By using immutable state, it becomes a lot simpler to:
compare previous and new state
implement an undo-redo mechanism
debug the application state
Good practices
DON'T update the state of a StateNotifier outside the notifier
While you could technically write:
classCounterextendsStateNotifier<int> {
Counter(): super(0);
}
final notifier = Counter();
notifier.state++;
That is considered an anti-pattern (and your IDE should show a warning).
Only the StateNotifier should modify its state. Instead, prefer using a method:
Why are listeners called when the new state is == to the previous state?
You may realize that a StateNotifier does not use == to verify that
the state has changed before notifying for changes.
This behavior is voluntary, for performance reasons.
The reasoning is that StateNotifier is typically used with complex objects,
which often override == to perform a deep comparison.
But performing a deep comparison can be a costly operation, especially since
it is common for the state to contain lists/maps.
Similarly, for complex states, it is rare that when calling notifier.state = newState,
the new and previous states are the same.
As such, instead of using ==, StateNotifier relies on identical to compare
objects.
This way, when using StateNotifier with simple states like int/enums, it will
correctly filter identical states. At the same time, this preserves performance
on complex states, as identical will not perform a deep object comparison.
Using custom notification filter logic
You can override the method updateShouldNotify(T old,T current) of a StateNotifier to change the default behaviour, such as for:
using == instead of identical to filter updates, for deep state comparison
always returning true to revert to older behaviors of StateNotifier
@overrideboolupdateShouldNotify(User old, User current) {
/// only update the User content changes, even if using a different instancereturn old.name != current.name && old.age != current.age;
}
Usage
Integration with Freezed
While entirely optional, it is recommended to use StateNotifier in combination
with Freezed. Freezed is a code-generation package for data-classes in Dart, which
automatically generates methods like copyWith and adds support for union-types.
A typical example would be using Freezed to handle data vs error vs loading states.
With its union-types, it can lead to a significant improvement in maintainability as
it:
ensures that your application will not enter illogical states
(such as both having a "data" and being in the "loading" state)
ensures that logic handles all possible cases. Such as forcing that the
loading/error cases be checked before trying to access the data.
The idea is that, rather than defining the data, error and loading state in a single
object like:
When using LocatorMixin, you may want to mock a dependency for your tests.
Of course, we still don't want to depend on Flutter/provider to do such a thing.
Similarly, since state is protected, tests need a simple way to read the state.
As such, LocatorMixin also adds extra utilities to help you with this scenario:
As such, if we want to test our previous Counter, we could mock LocalStorage
this way:
test('increment and saves to local storage', () {
final mockLocalStorage = MockLocalStorage();
final counter = Counter()
..debugMockDependency<LocalStorage>(mockLocalStorage);
expect(counter.debugState, 0);
counter.increment(); // works fine since we mocked the LocalStorageexpect(counter.debugState, 1);
// mockito stuffverify(mockLocalStorage.writeInt('int', 1));
});
Note:LocatorMixin only works on StateNotifier. If you try to use it on other classes by using with LocatorMixin, it will not work.