Okito는 상태 관리, 네비게이션, 로컬라이제이션, 종속성 주입 및 로컬 스토리지와 같은 핵심 기능을 통합하여 개발을 간소화하기 위해 설계된 포괄적인 Flutter 유틸리티 패키지입니다. 개발자들이 보일러플레이트 코드를 줄이고 코드 구조를 개선할 수 있도록 도와주는 플러터 개발자 전용 원스톱 헬퍼 역할을 합니다. 직관적인 API를 통해 일반적인 작업을 단순화하여 유연성과 성능을 희생하지 않고 더 빠른 앱 개발을 가능하게 합니다. 모듈식 설계로 개발자는 필요에 따라 필요한 구성 요소만 사용할 수 있습니다.
사용 사례
빠른 프로토타이핑
풀스택 플러터 앱
상태 관리 간소화
크로스 플랫폼 로컬라이제이션
의존성 주입 설정
로컬 데이터 지속성
주요 기능
내장 상태 관리
통합 탐색 시스템
자동 종속성 주입
로컬라이제이션 지원
안전한 로컬 저장소
최소한의 보일러플레이트
적합한 대상
초보자 플러터 개발자
효율을 추구하는 경력 있는 개발자
여러 유틸리티가 필요한 앱
일관된 아키텍처를 추구하는 팀
엄격한 마감일이 있는 프로젝트
고려 사항
부분 기능만 사용하는 경우 오버헤드가 발생할 수 있습니다
기존 패키지에 비해 문서가 제한적
신규 사용자를 위한 학습 곡선 가능성
매우 전문적인 상태 솔루션에는 적합하지 않음
고급 기능을 위해 제3자 통합에 의존합니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
okito
Your best flutter coding friend. All in one; state management, navigation management(with dynamic routing), local storage, dependency injection, localization, cool extensions with best usages and with the support of best utilities!
Q: What should I do to make it work?
A: Just create a regular class and extend OkitoController, if you want to change the state, call update() or setState()
Q: How does these methods notify the state?
A: They have a conversation between themselves, whenever you call these methods, the notifier checks all the builders, if they are watching, they will be re-built. So the scheme is;
Model -> Controller -> Model
View -> Controller -> View
The controller is the root of them.
You can also use setStateAsync function to update data asynchronously.
Use Controller
// That simple!OkitoBuilder(
controller: counterController,
builder: () => Text('${counterController.count}'),
);
Update Controller
main(){
// You can change state from anywhere without context!
counterController.increment();
}
// In FlutterElevatedButton(
onPressed: counterController.increment,
child: constText('Increment'),
)
// OrElevatedButton(
onPressed: () => counterController
.setState(() => counterController.count--),
child: constText('Decrement'),
)
Rockitos
Rockitos are our way to use state management with dependency injection!
If you don't know what is dependency injection, read Dependency Injection Guide first.
// Rockito - My favorite way to use Okito state.Rockito<CounterController>(
(controller) => Text('${controller.count}')
);
// Isn't it simple ?// To use _Rockito_, you should first inject the CounterController.Okito.inject(CounterController);
// RockitoBuilder - Rockito but with more features and builder!RockitoBuilder<CounterController>(
inject: CounterController(), // optionally, if you didn't inject it yet.
builder: (controller) => Text('${controller.count}'),
// You can use all of OkitoBuilder features here like otherControllers and etc.
);
Watch Controller
OkitoWatcher(
watch: counterController,
onChange: (CounterController controller) {
// You can also update the state there.// onChange gives to you is the instance of controller.print(controller.count);
},
);
counterController.increment();
// OkitoWatcher also returns a function that stops watching which// reduces the memory usage, you can use it when your usage ends.final stopWatching = OkitoWatcher(/* code here */);
// do what you want to do while watching, then:stopWatching();
// You can also watch with Rockitos.RockitoWatcher<CounterController>(
(controller) => print(controller.count))
// You have to inject the controller first.
State Methods
State methods are methods like the State class of Flutter.
classCounterControllerextendsOkitoController{
int count = 0;
// This will be called whenever your [OkitoBuilder] is mounted// to the widget tree.@overridevoidinitState() {
count++;
}
// This will be called whenever your [OkitoBuilder] is removed// from the widget tree.@overridevoiddispose() {
count = 0;
}
// This method will only be called when you use inject method of Okito.@overridevoidonInject() {
count++;
}
}
I personally use State Methods when I use controllers as StatefulWidget replacers.
Example:
classEditProductControllerextendsOkitoController{
// other nodes herefinal priceFocusNode = FocusNode();
voidsubmitForm(){
// I use my inputControllers here to get their values.// then I use my Okito routing without context!Okito.pushNamed('/productUpdatedSuccessfully/31')
}
@overridevoiddispose() {
// other nodes here to [dispose].
priceFocusNode.dispose();
}
}
Streams
You might have streams in your app and you might want to watch them, update state on changes etc.
This streams can also be Firestore streams, you can use it, too.
You might want to check examples folder for an example of it.
Firstly, we have to use OkitoStreamMixin
classYourControllerextendsOkitoControllerwithOkitoStreamMixin{
// your code, totally same.
}
// For regular streams.classCounterControllerextendsOkitoControllerwithOkitoStreamMixin{
int count = 0;
@overridevoidonInject(){
initStream<int>(stream: yourIntStream, onData(data) {
count = data; // You don't need to do anything or update.
});
}
}
// For Firestore query stream// To use, you have to get the snapshot as Stream of query.classPeopleControllerextendsOkitoControllerwithOkitoStreamMixin{
List<Person> people = [];
@override// Alternatively, you can use the constructor.voidonInject(){
initFirestoreQueryStream(querySnapshot: querySnapshot, onData(data) {
// This data is a list of documents(it is a map) of your collection.
people = data;
});
}
}
// For Firestore query stream// To use, you have to get the snapshot as Stream of query.classPersonControllerextendsOkitoControllerwithOkitoStreamMixin{
Person person = Person();
@override// Alternatively, you can use the constructor.voidonInject(){
initFirestoreDocumentStream(
documentSnapshot: documentSnapshot,
onData(data) {
// This data is your document as map.
person = Person.fromMap(data);
});
}
}
Utilities
Navigation and using widgets without context.
Firstly, we should wrap our app with Okito or provide Okito
// Basically, you should add *Okito* to the beginning of your app or provide key/observers manually.OkitoMaterialApp(/* Everything is same with [MaterialApp] */);
// OrOkitoCupertinoApp(/* Everything is same with [CupertinoApp] */);
// OrMaterial/CupertinoApp(
navigatorKey: Okito.navigatorKey,
navigatorObservers: [OkitoObserver()]);
Then you can use all of Okito Benefits!
All of the properities has same usages with its long usage
For example: Okito.pushNamed('/secondPage') = Navigator.of(context).pushNamed('secondPage')
And this id is a dynamic variable, right?
With Okito, you can do that easily!
// You don't need to add something like OkitoPage or etc.// Okito lets you do your job without code changes.OkitoMaterialApp(
routes: {
'/': (ctx) => FirstPage(),
'/second/:id': (ctx) => SecondPage(),
}
);
Now, whenever you try this:
ElevatedButton(
onPressed: () => Okito.pushNamed(
// You can add any kind of arguments'/second/33?name=Rago&postId=123&isMaterial=true',
arguments: 'This is an extra argument'),
child: constText('Go to second page'),
)
It will push to second page with the argument [id] : [33]
Moreover, you will see your arguments like this:
print(Okito.arguments);
// result
{'id' : '33', 'name' : 'Rago', 'postId' : 123, 'isMaterial' : true, 'arguments': 'This is an extra argument'};
// Yes, you can even get extra arguments manually.
Check example/flutter_dynamic_routing/lib/main.dart for a live example.
Would you like to have more benefits? Of course!
Theme Management
// Firstly, the bottom method gives you the app controller, you can update anything manually.Okito.app; /* or */Okito.use<AppController>();
// Then you have all of its usages.Okito.app.setThemeData();
Okito.app.setThemeMode();
Okito.app.setCupertinoThemeData();
Okito.app.locale;
Okito.app.setLocale();
Local Storage
OkitoStorage is a way to save variables to the local storage.
It works like SharedPereferences but it is synchronous like GetStorage.
OkitoStorage is blazingly fast because in read operations it uses memory to get data instead of reading from disk everytime!
// To use, you should init the storage, it is not required for web but required for all other platforms.voidmain() async{
// Only init is asynchronous, you can also call without await but it is not recommended.awaitOkitoStorage.init();
// Usagefinal box = OkitoStorage; // For easier reference.
box.write('count', 0);
finalint count = box.read<int>('count');
// Simple as this!print('Count is $count');
// Rest of your code will be here.
}
Other Usages
box.watchKey('count', () =>
print('This function will be called whenever the count changes.');
);
box.watchAll(() =>
print('This function will be called whenever the storage changes.');
);
box.removeKey('count'); // Removes the key
box.readAllKeys(); // returns all keys in storage
box.readAllValues(); // returns all values in storage
box.readAll(); // returns all of the storage
box.clearStorage(); // removes everything from the storage but storage will still exists.
box.deleteStorage(); // removes the storage from file system completely, after this operation, OkitoStorage won't be able to write or read.
Watch OkitoStorage With OkitoBuilder
// Check the example/flutter_okito_storage/lib/main.dart for more examples!// It will run whenever key 'count' changes.OkitoBuilder(
controller: yourController,
watchStorageKeys: ['count'],
builder: () => Text('${box.read('count')}'),
);
Benefits Of OkitoStorage
Really fast
You can watch the changes from anywhere, even in your builders.
It is synchronous, so you don't have to use 'await' keyword.
You can storage Strings, ints, Maps and even Lists.
Works on any device that flutter supports!
OkitoStorage is reliable but be careful when using it as database, because it is not created to be database. For complex works, you can try Hive!
Localization
For global apps, it might be hard to find a new library for localization or creating your own, then you will probably have another problems
like updating the whole app after language change and etc. Why would you do that?
Okito provides localization solutions for your app.
// It is also so simple to have!// Firstly create your translations like this:const translations = {
'en': {
'hello': 'Hello from Okito!',
},
'tr': {
'hello': "Okito'dan selamlar!",
},
};
// You can have unlimited amount of locales and translations// You can make it dynamic, seperate files and etc. It is just a dart map!
// After creating it, give it to the app.OkitoMaterialApp/* or OkitoCupertinoApp */(
translations: translations,
/* Your code here without any change */);
// Using it? It is the simplest! Lets use it in a text widget.Text('hello'.loc); // It will show 'Hello from Okito!'// Lets change the language and see it again.Okito.app.setLocale(Locale('tr','TR'));
// Now it says: 'Okito'dan selamlar!' as it is declared in translations.
// You can also set it like this;Okito.localize('hello'); // returns the translation as String.
For better examples check example/flutter_localization/lib/main.dart
Dependency injection is your way to inject variables to the Okito and use it anywhere in your app. With Okito, it is as simple as it can be!
// Example VariableclassCounter(){
count = 0;
}
// Inject itOkito.inject(Counter());
// Use it anywhere!Okito.use<Counter>();
// Asign it with type support!final counter = Okito.use<Counter>();
// Update however you want
counter.count++;
// orOkito.use<Counter>().count++;
Soo, lets say that your job is done with that class, why would we let it to use memory?
// Counter will be gone forever!Okito.eject<Counter>();
For more details, check the tests or examples about it!
Injection With Keys
It is useful when you want to storage with a String key instead of type which gives you advantage of multiple injects with same type.
// Example VariableclassCounter(){
count = 0;
}
// Inject itOkito.injectWithKey('firstCounter',Counter());
Okito.injectWithKey('secondCounter',Counter());
// Use it anywhere with type support!final firstCounter = Okito.useWithKey<Counter>('firstCounter');
final secondCounter = Okito.useWithKey<Counter>('secondCounter');
// Update however you want
firstCounter.count++;
secondCounter.count++;
Soo, lets say that your job is done with that class, why would we let it to use memory?
// Second counter will be gone forever!Okito.ejectWithKey('secondCounter');
Tips
Cleaner Widgets
// In your widgets folder or any other folder, declare builder.OkitoBuilderCounterBuilder({
requiredWidgetFunction() builder,
}) =>
OkitoBuilder(
controller: counterController,
builder: () => builder(),
);
// UsageCounterBuilder(builder: () => Text('${counterController.count}'));
classCounterControllerextendsOkitoController{
int _count = 0;
intget count => _count;
setcount(int count) {
_count = count;
// Now, whenever you change count like 'count++', it will update state.update();
}
}
App Controller
It is the controller of app, you can wrap your widgets that you want to change on big updates like theme changes if the data
you wrote is not coming from a controller.