Lite State는 Flutter용 가벼운 상태 관리 솔루션으로, 최소한의 부가 작업을 통해 상태 처리를 간단하게 만듭니다. 상태 전환을 관리하기 위한 간단한 기계 기반 접근 방식을 제공하여 애플리케이션 상태를 예측 가능하게 추적하고 업데이트하는 것을 쉽게 만듭니다. 소규모에서 중간 규모의 앱에 적합하며, 복잡한 설정이나 광범위한 학습 없이도 단순성과 성능을 강조합니다. 이 패키지는 기존의 Flutter 프로젝트에 원활하게 통합되며 모든 주요 플랫폼을 지원합니다.
사용 사례
작은 앱에서의 간단한 상태 관리
UI 상태 전환 관리
큰 상태 솔루션의 가벼운 대안
실시간 데이터 동기화
상태 기반 UI 업데이트
주요 기능
최소한의 보일러플레이트
기계 기반 상태 로직
크로스 플랫폼 지원
Flutter와의 간편한 통합
경량이고 빠름
적합한 대상
초보자 플러터 개발자
간단한 상태 제어가 필요한 프로젝트
무거운 상태 라이브러리를 피하는 앱
프로토타입 및 MVP
개발 속도를 우선시하는 팀
고려 사항
매우 대규모 애플리케이션에 적합하지 않음
제한된 내장 디버깅 도구
내장된 지속성 없음
수동 상태 직렬화 필요
기능이 풍부한 상태 관리자보다 기능이 적음
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Flutter Lite State
The litest state machine ever. You don't need to depend on any heavy libraries
if you only need to control state of your widgets.
This state manager is for those who are tired of writing
lots of boilerplate code of states like in case of using BLoC,
or don't want to depend on BuildContext like with using Provider
or just don't want tons of functionality in one place like with using Get.
I personally do like those state managers but I also see their disadvantages in some usecases. They just don't fit some projects. That's why I decided to write LiteState for the projects that need to be up and running in just a couple of minutes
Lite State is very simple and lite. It consists of a single file.
The purpose of LiteState is to make writing and using controllers as
quick and easy as possible. I wish I could do them even simplier
by using reflection, so that one don't even need to instantiate controllers
but unfortunately, Flutter doesn't support mirrors :(
Anyway, just take a look at the example below
You might also like my other packages
flutter_multi_formatter
flutter_instagram_storyboard
Features
Easily controlling the state of your widgets without a need for context. That's it.
Getting started
All your Lite State controllers must be inherited from LiteStateController
generic class. All the magic is happening inside.
Create a controller you need. E.g. an auth controller to check users'
authorization
Add initialization of your controller somewhere in the beginning of your app
e.g. in initState() {} of your main class
voidmain() {
runApp(constMyApp());
}
classMyAppextendsStatefulWidget{
constMyApp({super.key});
@overrideState<MyApp> createState() => _MyAppState();
}
class_MyAppStateextendsState<MyApp> {
@overridevoidinitState() {
/// In this case I've used lazy initialization/// this means the controller will be instantiated when it's first /// used. Pay attention, I've used AuthController type as a key /// in the instantiator Map. This is used internally to look for /// a controller using generic constraints/// In case you want to initialize all your controllers at once/// just use initControllers() instead of initControllersLazy()initControllersLazy({
AuthController:() => AuthController(),
LoaderController:() => LoaderController(),
});
super.initState();
}
@overrideWidgetbuild(BuildContext context) {
returnMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TestPage(),
);
}
}
Just you LiteState builder any where you need
Widgetbuild(BuildContext context) {
returnLiteState<AuthController>(
builder: (BuildContext c, AuthController controller) {
/// ... your code goes here
},
);
}
OR
You can use a single use controller like this
BE AWARE that in this case you controller will be disposed as soon
as LiteState widget, where you pass it, is disposed
The controller like this will override any other controller of the same time
that you might have initialized via initControllers or initControllersLazy
It's also possible to use the same controller type for many widgets. And the controller instances will not
conflict with each other. In this case the setup is almost the same as the above but along with the instance of the controller you need to pass true to the useIsolatedController parameter of the LiteState widget.
classTestPageextendsStatelessWidget{
constTestPage({Key? key}) : super(key: key);
Widget_buildUserName() {
/// I use authController global property here instead of/// calling findController<AuthController>()/// but it does call findController inside. It's just a shorhandreturnText('Welcome, ${authController.userName}');
}
@overrideWidgetbuild(BuildContext context) {
returnLiteState<AuthController>(
builder: (BuildContext c, AuthController controller) {
returnScaffold(
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildUserName(),
constSizedBox(height: 20.0),
Button(
isLoading: controller.isLoading,
text: controller.isAuthorized ? 'Log Out' : 'Authorize',
onPressed: () {
/// you can also use a shorthand method fc<AuthController>()/// to do the same. It calls findController internally/// or just write a global function to get it./// e.g. right above AuthController declaration/// AuthController get authController {/// return findController<AuthController>();/// }if (controller.isAuthorized) {
findController<AuthController>().logout();
} else {
findController<AuthController>().authorize();
}
},
),
constPadding(
padding: EdgeInsets.only(
left: 40.0,
right: 40.0,
top: 20.0,
),
child: Text(
'Notice that your authorization is stored across sessions in a persistant local storage. Reload the app and you will see that authorization is still there',
),
),
],
),
),
);
},
);
}
}
Controllers also allow you to easily store and restore
data from shared preferences. When the local data is restored
a controller calls void onLocalStorageInitialied(); method.
You can override it in your controllers to know the exact time
when your local data is ready.
This example shows how you can store and restore auth tokens
/// First you need to create a class that you want to/// be storable / restorable. The class must implement /// LSJsonEncodable interface. It's very simple and is needed /// for a LiteState controller to determin if and instance of the /// class can be serialized according to its rules. /// The interface only contains one method: Map encode(); /// which must be overriden in your classclassAuthDataimplementsLSJsonEncodable{
String type;
String token;
String userName;
AuthData({
requiredthis.type,
requiredthis.token,
requiredthis.userName,
});
@overrideMapencode() {
return {
'type': type,
'token': token,
'userName': userName,
};
}
/// The static method is necessary as a decoder /// so that a controller can understand how to work with your data/// when it meet the data in SharedPreferences/// You can see how it's used belowstaticAuthDatadecode(Map map) {
returnAuthData(
type: map['type'],
token: map['token'],
userName: map['userName'],
);
}
}
Now you need to initialize your decoders. The best place to do it is
right before you initialize your controllers. Somewhere in the beginning of your app
@overridevoidinitState() {
/// Initialize decoders. In this case we only have AuthData decoder/// but you will need to add decoders for every class you want to be encodable / decodableinitJsonDecoders({
AuthData: AuthData.decode,
});
initControllersLazy({
AuthController:() => AuthController(),
LoaderController:() => LoaderController(),
});
super.initState();
}
That's basically it. Now you can simply use it in your controller.
If you want to rebuild only a particular builder, you can give this name to a LiteState instance amd pass the same name to the rebuild('someBuilderName') method. This will trigger only a particular builder. If you don't pass a name, all builders for this controllers that are currently in the widget tree will be rebuilt.
Here's the complete example. As you can see, it uses a few instances of LiteState with the same controller on the same page. But the instances have different names. And when you press a corresponding button, you will see only one print with the related information, which means that only that builder was triggered.
import'package:example/button.dart';
import'package:flutter/material.dart';
import'package:lite_state/lite_state.dart';
classNamedBuildersPageextendsStatefulWidget{
constNamedBuildersPage({super.key});
@overrideState<NamedBuildersPage> createState() => _NamedBuildersPageState();
}
class_NamedBuildersPageStateextendsState<NamedBuildersPage> {
finalNamedBuilderController _controller = NamedBuilderController();
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar: AppBar(
title: constText('Named Builders Page'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
constSizedBox(
height: 100.0,
),
/// Press one of two buttons and see how only a particular print will work/// While the builders use the same instance of controllerLiteState<NamedBuilderController>(
controller: _controller,
builderName: 'text1',
builder: (BuildContext c, NamedBuilderController controller) {
debugPrint('REBUILD TEXT 1');
returnconstText('Text1');
},
),
constSizedBox(
height: 20.0,
),
LiteState<NamedBuilderController>(
controller: _controller,
builderName: 'text2',
builder: (BuildContext c, NamedBuilderController controller) {
debugPrint('REBUILD TEXT 2');
returnconstText('Text2');
},
),
constSizedBox(
height: 20.0,
),
Button(
text: 'Rebuild Text1',
onPressed: () {
_controller.rebuild('text1');
},
),
Button(
text: 'Rebuild Text2',
onPressed: () {
_controller.rebuild('text2');
},
),
Button(
text: 'Unnamed Rebuild',
onPressed: () {
_controller.rebuild();
},
),
],
),
),
);
}
}
classNamedBuilderControllerextendsLiteStateController<NamedBuilderController> {
@overridevoidreset() {}
@overridevoidonLocalStorageInitialized() {}
}