flutter_redux는 Flutter 앱에서 예측 가능한 상태 관리를 가능하게 하기 위해 UI 컴포넌트를 Redux 스토어에 연결하는 유틸리티 위젯을 제공하는 Flutter 패키지입니다. `StoreProvider`, `StoreBuilder`, `StoreConnector` 위젯을 통해 위젯 트리 전체에서 상태 변경에 쉽게 접근하고 반응할 수 있습니다. Redux 패턴을 사용하는 개발자를 위한 것으로, 애플리케이션 상태와 액션을 중앙 집중화함으로써 관심사의 분리를 촉진합니다. Dart의 반응형 프로그래밍 모델과 원활하게 통합되며, Android, iOS, Web, macOS, Windows, Linux 등 모든 주요 플랫폼을 지원합니다.
사용 사례
글로벌 앱 상태 관리
일방향 데이터 흐름 구현
UI를 Redux 스토어에 연결하기
확장 가능한 플러터 앱 빌드
상태 로직을 독립적으로 테스트하기
주요 기능
의존성 주입을 위한 StoreProvider
반응형 상태 업데이트를 위한 StoreBuilder
StoreConnector는 세밀한 상태 구독을 위해 사용됩니다
미들웨어 및 리듀서 지원
크로스 플랫폼 호환성
적합한 대상
대규모 플러터 애플리케이션
Redux 아키텍처를 사용하는 팀
중앙 집중식 상태 관리가 필요한 앱
Redux 패턴에 익숙한 개발자
테스트 가능한 상태 로직이 필요한 프로젝트
고려 사항
Redux 개념을 이해해야 합니다
더 간단한 상태 솔루션과 비교해 보일러플레이트가 추가될 수 있음
작은 또는 간단한 앱에는 적합하지 않음
자주 상태 업데이트 시 성능 영향
초보자를 위한 학습 곡선이 더 가파릅니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
flutter_redux
A set of utilities that allow you to easily consume a
Redux Store to build Flutter Widgets.
This package supports null-safety and is built to work with
Redux.dart 5.0.0+ and Flutter 3+.
Redux Widgets
StoreProvider - The base Widget. It will pass the given Redux Store to all descendants that request it.
StoreBuilder - A descendant Widget that gets the Store from a StoreProvider and passes it to a Widget builder function.
StoreConnector - A descendant Widget that gets the Store from the nearest StoreProvider ancestor, converts the Store into a ViewModel with the given converter function, and passes the ViewModel to a builder function. Any time the Store emits a change event, the Widget will automatically be rebuilt. No need to manage subscriptions!
Examples
Simple example - a port of the standard "Counter Button" example from Flutter
Github Search - an example of how to search as a user types, demonstrating both the Middleware and Epic approaches.
Todo app - a more complete example, with persistence, routing, and nested state.
Timy Messenger - large open source app that uses flutter_redux together with Firebase Firestore.
Let's demo the basic usage with the all-time favorite: A counter example!
Note: This example requires flutter_redux 0.4.0+ and Dart 2! If you're using
Dart 1, see the old
example.
import'package:flutter/material.dart';
import'package:flutter_redux/flutter_redux.dart';
import'package:redux/redux.dart';
// One simple action: IncrementenumActions { Increment }
// The reducer, which takes the previous count and increments it in response// to an Increment action.intcounterReducer(int state, dynamic action) {
return action == Actions.Increment ? state + 1 : state;
}
voidmain() {
// Create your store as a final variable in the main function or inside a// State object. This works better with Hot Reload than creating it directly// in the `build` function.final store = Store<int>(counterReducer, initialState: 0);
runApp(FlutterReduxApp(
title: 'Flutter Redux Demo',
store: store,
));
}
classFlutterReduxAppextendsStatelessWidget{
finalStore<int> store;
finalString title;
FlutterReduxApp({Key key, this.store, this.title}) : super(key: key);
@overrideWidgetbuild(BuildContext context) {
// The StoreProvider should wrap your MaterialApp or WidgetsApp. This will// ensure all routes have access to the store.returnStoreProvider<int>(
// Pass the store to the StoreProvider. Any ancestor `StoreConnector`// Widgets will find and use this value as the `Store`.
store: store,
child: MaterialApp(
theme: ThemeData.dark(),
title: title,
home: Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Connect the Store to a Text Widget that renders the current// count.//// We'll wrap the Text Widget in a `StoreConnector` Widget. The// `StoreConnector` will find the `Store` from the nearest// `StoreProvider` ancestor, convert it into a String of the// latest count, and pass that String to the `builder` function// as the `count`.//// Every time the button is tapped, an action is dispatched and// run through the reducer. After the reducer updates the state,// the Widget will be automatically rebuilt with the latest// count. No need to manually manage subscriptions or Streams!StoreConnector<int, String>(
converter: (store) => store.state.toString(),
builder: (context, count) {
returnText(
'The button has been pushed this many times: $count',
style: Theme.of(context).textTheme.display1,
);
},
)
],
),
),
// Connect the Store to a FloatingActionButton. In this case, we'll// use the Store to build a callback that will dispatch an Increment// Action.//// Then, we'll pass this callback to the button's `onPressed` handler.
floatingActionButton: StoreConnector<int, VoidCallback>(
converter: (store) {
// Return a `VoidCallback`, which is a fancy name for a function// with no parameters and no return value. // It only dispatches an Increment action.return () => store.dispatch(Actions.Increment);
},
builder: (context, callback) {
returnFloatingActionButton(
// Attach the `callback` to the `onPressed` attribute
onPressed: callback,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
),
),
),
);
}
}
My advice is the same as the original Redux.JS author: If you've got a simple
app, use the most straightforward thing possible. In Flutter, StatefulWidget is perfect
for a simple counter app.
However, say you have the more complex app, such as an E-commerce app with a
Shopping Cart. The Shopping Cart should appear on multiple screens in your app
and should be updated by many different types of Widgets on those different
screens (An "Add Item to Cart" Widget on all your Product Screens, "Remove Item
from Cart" Widget on the Shopping Cart Screen, "Change quantity" Widgets, etc.).
Additionally, you want to test this logic, as it's the core business
logic to your app!
Now, in this case, you could create a Testable ShoppingCart class as a
Singleton or Create a Root StatefulWidget that passes the ShoppingCart Down
Down Down through your widget hierarchy to the "add to cart" or "remove from
cart" Widgets.
Singletons can be problematic for testing, and Flutter doesn't have a great
Dependency Injection library (such as Dagger2) just yet, so I'd prefer to avoid
those.
Yet passing the ShoppingCart all over the place can get messy. It also means
it's way harder to move that "Add to Item" button to a new location b/c you'd
need up update the Widgets throughout your app that passes the state down.
Furthermore, you'd need a way to Observe when the ShoppingCart Changes so you
could rebuild your Widgets when it does (from an "Add" button to an "Added"
button, as an example).
One way to handle it would be to simply setState every time the ShoppingCart
changes in your Root Widget, but then your whole app below the RootWidget would
be required to rebuild as well! Flutter is fast, but we should be thoughtful about
what we ask Flutter to rebuild!
Therefore, redux & redux_flutter was born for more complex stories like this
one. It gives you a set of tools that allow your Widgets to dispatch actions
in a naive way, then writes the business logic in another place that will take
those actions and update the ShoppingCart in a safe, testable way.
Even more, once the ShoppingCart has been updated in the Store, the Store
will emit an onChange event. This lets you listen to Store updates and
rebuild your UI in the right places when it changes! Now, you can separate your
business logic from your UI logic in a testable, observable way, without having
to Wire up a bunch of stuff yourself!
Similar patterns in Android are the MVP Pattern or using Rx Observables to
manage a View's State.
flutter_redux handles passing your Store down to all of your
descendant StoreConnector Widgets. If your State emits a change event, only
the StoreConnector Widgets and their descendants will be automatically rebuilt
with the latest State of the Store!
This allows you to focus on what your app should look like and how it should
work without thinking about all the glue code to hook everything together!