Orientação gerada a partir de informações públicas. Apenas para referência.
Katana Router é um pacote Flutter/Dart que utiliza o build_runner para gerar automaticamente configurações de roteamento seguras por tipo. Simplifica a navegação ao criar rotas verificadas em tempo de compilação, reduzindo erros em tempo de execução e melhorando a produtividade do desenvolvedor. O pacote se integra perfeitamente aos aplicativos Flutter em múltiplas plataformas, incluindo Android, iOS, Web, macOS, Windows e Linux. Ao gerar definições de rota a partir de anotações, garante consistência e melhora a manutenibilidade do código. Ideal para projetos que exigem estruturas de navegação robustas e escaláveis.
Casos de uso
Navegação segura por tipo em aplicativos grandes do Flutter
Geração automática de rotas com build_runner
Configuração de navegação multiplataforma
Reduzindo erros de navegação em tempo de execução
Gerenciamento de rota simplificado em aplicativos complexos
Principais recursos
Geração de rota em tempo de compilação
Segurança de tipo em tempo de compilação
Definição de rota baseada em anotações
Suporte multiplataforma
Integração com build_runner
Boilerplate mínimo
Indicado para
Desenvolvedores priorizando segurança de tipo
Equipes gerenciando navegação complexa
Projetos usando fluxos de trabalho do build_runner
Aplicativos direcionados a múltiplas plataformas
Grandes aplicações com roteamento estruturado
Considerações
Requer configuração do build_runner
Curva inicial de aprendizado para anotações
Personalização limitada sem geração de código
Dependente da estabilidade do processo de compilação
Pode aumentar levemente o tempo de compilação
README original
Snapshot do README original do repositório-fonte vinculado. Consulte o repositório para o conteúdo mais recente.
Here is a video of a notepad application being completed in just 5 minutes from a completely empty state.
By using the Masamune framework, it is possible to reduce most of the coding that is done in application development.
This framework focuses on the following functions
Generation of code templates by CLI (command line interface) tools
Automatic generation of additional code by build_runner
In other words, by having the majority of the code generated mechanically, the human coding part is reduced as much as possible.
In addition, the human coding part is almost type-safe, allowing implementation without hesitation with the support of the IDE's suggestion function and other features.
The following benefits can be enjoyed by offering these features
Faster implementation
Faster implementation because fewer parts are actually coded.
Fewer coding errors
Since fewer parts are actually coded, the probability of making a mistake is also reduced by that amount.
Easier to follow the source code
It is easier to follow the source later after some time has passed because there is less actual coding to do.
Differences in codes between people are difficult to distinguish
Coding is done according to the generated template, so it is difficult to have different codes for different people.
Facilitates team development
Since it is difficult to differentiate codes among people, it is easier to check codes and assign personnel within the team.
In addition, this framework provides the following functions to support application development from many angles
Routing
Web URL support, conditional redirection, and nested navigation are also available.
Database
NoSQL database based on Firestore structure.
Local and Firestore can be easily switched by switching adapters.
State management
State management in a simple form like flutter_hooks.
Translation
Translation management using Google Spreadsheets.
Theme management
You can define the colors and text of the theme.
Image and font files can be retrieved type-safe in code.
Shared preferences
A data store using SharedPreferences is available in addition to the databases listed above.
Form building
Support data input from users, mainly forms, from the UI level.
UI support
It provides functions to implement list widgets and simple modals that can update widgets with less load when data is updated.
Firebase/Firestore support
Provides functionality to easily switch between Firebase features such as Authentication, Cloud Firestore, Cloud Storage, and Firebase DataConnect.
By using this framework, for example, a simple CRUD application requires only the following parts to be implemented.
DataScheme
Only the type and variable name (and possibly the initial value) need be defined.
View
Build the application design with widgets. (Support is provided for some elements, such as forms.)
Data binding, for example, can be easily performed.
Installation
Install the CLI with the following command.
flutter pub global activate katana_cli
To install Masamune Framework in an existing project, add the package with the following command.
Basically, it is the same as flutter create, but the following changes are made automatically.
You can automatically place image files under the assets folder.
katana.yaml will be placed.
Required packages are installed automatically.
The launcher settings for VSCode are automatically set.
main.dart is rewritten and code generation is performed by build_runner.
Code Change Monitoring
It uses build_runner's monitoring function to detect changes in the target code and automatically generates the code immediately if any changes are made.
The code analysis of build_runner itself is very slow, so the developer experience would be much better if it were constantly monitored.
To monitor the code, execute the following command in a separate terminal.
katana code watch
When the command is typed, it enters the monitoring state and is left alone.
(If you do not do this, you will not be able to type the code template creation command described below, so please start it in a different terminal.)
If code monitoring is not performed, enter the following command as appropriate.
katana code generate
Implementation
Page
Create Page
To create a screen (page) for the application, execute the following command.
katana code page [Page name]
A file named (Page name).dart will be created under lib/pages.
A class named (Page name)Page is created as follows
Describe the contents of the screen UI inside the build, just as you would with a StatelessWidget or a StatefulWidget.
// test.dart// ignore: unused_import, unnecessary_importimport"package:flutter/material.dart";
// ignore: unused_import, unnecessary_importimport"package:masamune/masamune.dart";
// ignore: unused_import, unnecessary_importimport"package:masamune_universal_ui/masamune_universal_ui.dart";
// ignore: unused_import, unnecessary_importimport"package:xxxx/main.dart";
part"test.page.dart";
@immutable// TODO: Set the path for the page.@PagePath("test")
classTestPageextendsPageScopedWidget{
constTestPage({
super.key,
// TODO: Set parameters for the page.
});
// TODO: Set parameters for the page in the form [final String xxx]./// Used to transition to the TestPage screen.////// ```dart/// router.push(TestPage.query(parameters)); // Push page to TestPage./// router.replace(TestPage.query(parameters)); // Replace page to TestPage./// ```@pageRouteQuerystaticconst query = _$TestPageQuery();
@overrideWidgetbuild(BuildContext context, PageRef ref) {
// Describes the process of loading// and defining variables required for the page.// TODO: Implement the variable loading process.// Describes the structure of the page.// TODO: Implement the view.returnUniversalScaffold();
}
}
You can specify the deep link path by specifying the path in @PagePath("").
@PagePath("user/:user_id")
If the page requires an argument, simply add the parameter as follows.
constTestPage({
super.key,
// TODO: Set parameters for the page.requiredthis.name,
this.text,
});
// TODO: Set parameters for the page in the form [final String xxx].finalString name;
finalString? text;
~~~~~~~~~~~~~~~~~~
Page Transition
Page transitions are performed using the router defined in main.dart.
You can specify a query already described in the created class to transition to that page.
// Transition to TestPage
router.push(TestPage.query());
// Page Replacements
router.replace(TestPage.query());
// Back to Previous Page
router.pop();
Initial Page Setup
You can set the page when the application is launched by passing the query you want to set for the initial page to the initialQuery defined in main.dart.
/// Initial page query.
// TODO: Define the initial page query of the application.
final initialQuery = TestPage.query();
To learn more about the other features listed below, please visit the package details page.
To create a collection model, enter the following command
katana code collection [Collection name]
A file named (Collection name).dart will be created under lib/models.
A class named (Collection name)Model is created as follows
If a collection model is created with the above command, a document model will also be available with the same data scheme.
// test.dart// ignore: unused_import, unnecessary_importimport"package:flutter/material.dart";
// ignore: unused_import, unnecessary_importimport"package:masamune/masamune.dart";
// ignore: unused_import, unnecessary_importimport"package:xxxx/main.dart";
import"package:freezed_annotation/freezed_annotation.dart";
part"test.m.dart";
part"test.g.dart";
part"test.freezed.dart";
/// Value for model.@freezed@formValue@immutable// TODO: Set the path for the collection.@CollectionModelPath("test")
abstractclassTestModelwith_$TestModel{
constfactoryTestModel({
// TODO: Set the data schema.
}) = _TestModel;
constTestModel._();
factoryTestModel.fromJson(Map<String, Object?> json) => _$TestModelFromJson(json);
/// Query for document.////// ```dart/// appRef.model(TestModel.document(id)); // Get the document./// ref.app.model(TestModel.document(id))..load(); // Load the document./// ```staticconstdocument = _$TestModelDocumentQuery();
/// Query for collection.////// ```dart/// appRef.model(TestModel.collection()); // Get the collection./// ref.app.model(TestModel.collection())..load(); // Load the collection./// ref.app.model(/// TestModel.collection().data.equal(/// "data",/// )/// )..load(); // Load the collection with filter./// ```staticconst collection = _$TestModelCollectionQuery();
/// Query for form value.////// ```dart/// ref.app.form(TestModel.form(LogModel())); // Get the form controller in app scope./// ref.page.form(TestModel.form(LogModel())); // Get the form controller in page scope./// ```staticconst form = _$TestModelFormQuery();
}
/// [Enum] of the name of the value defined in TestModel.typedefTestModelKeys = _$TestModelKeys;
/// Alias for ModelRef<TestModel>.////// When defining parameters for other Models, you can define them as follows////// ```dart/// @RefParam(TestModelDocument) TestModelRef test/// ```typedefTestModelRef = ModelRef<TestModel>?;
/// It can be defined as an empty ModelRef<TestModel>.////// ```dart/// TestModelRefPath("xxx") // Define as a path./// ```typedefTestModelRefPath = _$TestModelRefPath;
/// Class for defining initial values to be passed to `initialValue` of [RuntimeModelAdapter].////// ```dart/// RuntimeModelAdapter(/// initialValue: [/// TestModelInitialCollection(/// "xxx": TestModel(...),/// ),/// ],/// );/// ```typedefTestModelInitialCollection = _$TestModelInitialCollection;
/// Document class for storing TestModel.typedefTestModelDocument = _$TestModelDocument;
/// Collection class for storing TestModel.typedefTestModelCollection = _$TestModelCollection;
/// It can be defined as an empty ModelRef<TestModel>.////// ```dart/// TestModelMirrorRefPath("xxx") // Define as a path./// ```typedefTestModelMirrorRefPath = _$TestModelMirrorRefPath;
/// Class for defining initial values to be passed to `initialValue` of [RuntimeModelAdapter].////// ```dart/// RuntimeModelAdapter(/// initialValue: [/// TestModelMirrorInitialCollection(/// "xxx": TestModel(...),/// ),/// ],/// );/// ```typedefTestModelMirrorInitialCollection = _$TestModelMirrorInitialCollection;
/// Document class for storing TestModel.typedefTestModelMirrorDocument = _$TestModelMirrorDocument;
/// Collection class for storing TestModel.typedefTestModelMirrorCollection = _$TestModelMirrorCollection;
If you do not need a collection, but only a document model, you can create one with the following command.
katana code document [Document name]
Specify the data path by specifying the contents of @CollectionModelPath("") (@DocumentModelPath("")).
As with Firestore, there are restrictions on path hierarchy.
(Odd: collection, Even: document)
@CollectionModelPath("user")
Also, list the variables that will be the data scheme in the factory constructor.
This is the content of the data that can be handled in this collection (document).
constfactoryTestModel({
// TODO: Set the data schema.requiredString name,
String? test,
}) = _TestModel;
Using Model
The created model can be handled by using ref.app.model from PageRef (WidgetRef) passed in the build method if it is within a page or widget created with ScopedWidget or Scoped.
The actual object can be obtained by passing a collection or document defined in the model created in ref.app.model.
Data can also be loaded from the database by executing the load method of the retrieved object.
When loading is complete or data is rewritten, the widget that executed ref.app.model is rebuilt.
@overrideWidgetbuild(BuildContext context, PageRef ref) {
// Describes the process of loading// and defining variables required for the page.// TODO: Implement the variable loading process.final testModelCollection = ref.app.model(TestModel.collection()); // Obtain a collection of TestModel.
testModelCollection.load(); // Load model data
~~~~~~~~~
}
Also, if you want to use it outside of a page or widget, you can use it in an appRef defined in main.dart.
final testModelCollection = appRef.model(TestModel.collection());
SharedPreferences feature
If you want to store data locally, such as SharedPreferences, separately from the regular DB, you can do so by using a ModelAdapter.
Google Spreadsheets can be used as a CSV data source.
Please use this for cases such as when you ask customers to set up data, etc. since non-engineers will be touching it.
Google Spreadsheets will be made available in advance.
Copy the spreadsheet from this template to your own Google Drive.
If you use CollectionModelPath, use the sheet for collections; if you use DocumentModelPath, use the sheet for documents.
You can copy from File -> Create Copy.
In the copied spreadsheet, click File -> Share -> Share with others.
In the Share (name of spreadsheet you created) window, change General Access to Everyone who knows the link.
Add the "GoogleSpreadSheetDataSource" annotation as an addition to the class you created, and copy the URL as it appears in the browser (e.g., https://docs.google.com/spreadsheets/d/1bfNX8clPH9 PFOcfFIStNCGNGjeCKwGv-24iChSJn8yM/edit#gid=0) and copy it.
/// Value for model.@freezed@formValue@immutable@GoogleSpreadSheetDataSource(
"https://docs.google.com/spreadsheets/d/1bfNX8clPH9PFOcfFIStNCGNGjeCKwGv-24iChSJn8yM/edit#gid=0",
version: 1,
)
// TODO: Set the path for the collection.@CollectionModelPath("test")
abstractclassTestModelwith_$TestModel{
constfactoryTestModel({
// TODO: Set the data schema.
}) = _TestModel;
constTestModel._();
To learn more about the other features listed below, please visit the package details page.
Create a controller when you want to use a controller already provided by Flutter, such as ScrollController or TextEditingController, in a page or widget, or when you want to make minor adjustments such as bundling data models.
katana code controller [Controller name]
A file named (Page name).dart will be created under lib/controllers.
A class named (Page name)Controller is created as follows.
// test.dart// ignore: unused_import, unnecessary_importimport"package:flutter/material.dart";
// ignore: unused_import, unnecessary_importimport"package:masamune/masamune.dart";
// ignore: unused_import, unnecessary_importimport"package:xxxx/main.dart";
part"test.m.dart";
/// Controller.@Controller(autoDisposeWhenUnreferenced: false)
classTestControllerextendsChangeNotifier{
TestController(
// TODO: Define some arguments.
);
// TODO: Define fields and processes./// Query for TestController.////// ```dart/// appRef.controller(TestController.query(parameters)); // Get from application scope./// ref.app.controller(TestController.query(parameters)); // Watch at application scope./// ref.page.controller(TestController.query(parameters)); // Watch at page scope./// ```staticconst query = _$TestControllerQuery();
}
When using PageRef (WidgetRef), you can obtain the actual object by passing query to ref.(page/app).controller().
For ref.app.controller, it is managed across multiple pages, while ref.page.controller is managed only within that specific page.
When defined in ref.app.controller, the controller basically won't be destroyed until the application is terminated.
When defined in ref.page.controller, the controller will be destroyed when that page is destroyed.
@overrideWidgetbuild(BuildContext context, PageRef ref) {
// Describes the process of loading// and defining variables required for the page.// TODO: Implement the variable loading process.final testController = ref.page.controller(TestController.query()); // Obtain TestController.
~~~~~~~~~
}
Internally, it inherits from ChangeNotifier, so when notifyLisnteners() is executed, the loaded widget is rebuilt.
It can also be used from outside the widget using appRef.
In the case of appRef, it is managed across pages. (Same as ref.app.controller)
final testController = appRef.controller(TestController.query());
State management
Basically, I think the above ref.app.model and ref.(page/app).controller can cover most of the state management.
State management can also be extended, so please see the package details page for more information.
To update translations, update the version in AppLocalize which is defined in main.dart.
@YamlLocalize(
version: 1, // Increment this version when updating.
)
classAppLocalizeextends_$AppLocalize{}
localize.app.yaml and localize.base.yaml are created in the project root, so please write translations in localize.app.yaml as shown below.
localize:# Apps-key:"AppTitle"en_US:"AppTitle"ja_JP:"アプリタイトル"zh_CN:"应用标题"ko_KR:"앱 제목"es_ES:"Título de la aplicación"fr_FR:"Titre de l'application"de_DE:"Anwendungstitel"pt_PT:"Título da aplicação"ru_RU:"Заголовок приложения"id_ID:"Judul aplikasi"-key:"SubTitle"en_US:"SubTitle"ja_JP:"サブタイトル"zh_CN:"字幕"ko_KR:"부제"es_ES:"Subtítulo"fr_FR:"Sous-titre"de_DE:"Untertitel"pt_PT:"Subtítulo"ru_RU:"Подзаголовок"id_ID:"Subjudul"
Execute the command below to generate code.
katanacodegenerate
To retrieve translation text, use the l object.
Text(l().success);
To learn more about the other features listed below, please visit the package details page.
// main.dart/// App Theme.////// ```dart/// theme.color.primary // Primary color./// theme.text.bodyMedium // Medium body text style./// theme.asset.xxx // xxx image./// theme.font.xxx // xxx font./// ```@appThemefinal theme = AppThemeData(
// TODO: Set the design.
primary: Colors.blue,
secondary: Colors.cyan,
onPrimary: Colors.white,
onSecondary: Colors.white,
);
Using Themes
The following themes can be obtained by using the theme in main.dart.
color
ColorScheme defined when creating AppThemeData.
text
TypeScale defined when creating AppThemeData.
asset
Assets under the assets folder created in code generation.
font
FontFamily created by Code Generation.
@overrideWidgetbuild(BuildContext context, PageRef ref) {
// Describes the process of loading// and defining variables required for the page.// TODO: Implement the variable loading process.// Describes the structure of the page.// TODO: Implement the view.returnUniversalScaffold(
appBar: UniversalAppBar(title: Text("Title"), backgroundColor: theme.color.secondary),
body: UniversalColumn(
crossAxisAlignment: CrossAxisAlignment.start,
children:[
Center(child: CircleAvatar(backgroundImage: theme.asset.userIcon.provider)),
Text("User Name", style: theme.text.displayMedium)
]
)
);
}
To learn more about the other features listed below, please visit the package details page.
First, obtain a form controller to control and hold the form values.
When creating a form that targets a data model, such as editing profile data, use a form that is defined in an existing data model.
/// Query for form value.////// ```dart/// ref.app.form(TestModel.form(LogModel())); // Get the form controller in app scope./// ref.page.form(TestModel.form(LogModel())); // Get the form controller in page scope./// ```staticconst form = _$TestModelFormQuery();
Like controllers, the form can be passed to both app scope (ref.app.form) and page scope (ref.page.form), but when doing so, you need to pass the original object (TestModel) as an argument.
When registering new data, simply create and pass a TestModel, and when editing existing data, pass the values read from the data model as they are.
// When creating new datafinal memo = constMemoModel(title: "", text: "");
final formController = ref.page.form(MemoModel.form( memo ));
// When creating existing datafinal memo = ref.app.model(MemoModel.document("Memo ID"))..load();
final formController = ref.page.form(MemoModel.form( memo ));
To create a form for data not defined in the data model, such as login, use the following command to create a data definition for the form.
katana code value [Value name]
The following file will be created in lib/models/(Value name).dart.
Internally, a class named (Value name)Value is created.
// login.dart// ignore: unused_import, unnecessary_importimport"package:flutter/material.dart";
// ignore: unused_import, unnecessary_importimport"package:masamune/masamune.dart";
// ignore: unused_import, unnecessary_importimport"package:xxxx/main.dart";
import"package:freezed_annotation/freezed_annotation.dart";
part"login.g.dart";
part"login.m.dart";
part"login.freezed.dart";
/// Immutable value.@freezed@formValue@immutableabstractclassLoginValuewith_$LoginValue{
constfactoryLoginValue({
// TODO: Set the data schema.
}) = _LoginValue;
constLoginValue._();
factoryLoginValue.fromJson(Map<String, Object?> json) =>
_$LoginValueFromJson(json);
/// Query for form value.////// ```dart/// ref.app.form(LoginValue.form(LogModel())); // Get the form controller in app scope./// ref.page.form(LoginValue.form(LogModel())); // Get the form controller in page scope./// ```staticconst form = _$LoginValueFormQuery();
}
Please add the necessary data scheme in the factory method.
constfactoryLoginValue({
// TODO: Set the data schema.requiredString email,
requiredString password,
}) = _LoginValue;
To retrieve the form controller, use the form defined in this object for the same purpose.
final login = constLoginValue(email: "", password: "");
final formController = ref.app.form(LoginValue.form( login ));
Form drawing and validation/finalization
Pass the above form controller to the form parameter of each widget for the form.
In doing so, please write a process to rewrite the form value and return it with the target value passed to the onSaved parameter.
After writing the form widget while including the above process, the form values are validated and confirmed by executing formController.validate when the confirm button is pressed.
After the verification passes, use the returned value to save the data.
FormButton(
"Login",
onPressed: () async {
finalLoginValue loginValue = formController.validate(); // Validate and get form valuesif (loginValue == null) {
return;
}
try {
// Normal processing
} catch (e) {
// Error handling
}
},
),
Please see the package details page for other details.
The dialog can be displayed with the following code.
// Alert dialog.Modal.alert(
title: "Title",
text: "Contents text",
submitText: "OK",
onSubmit: () {
// Processing when the OK button is pressed
},
);
// Confirmation dialog.Modal.confirm(
title: "Title",
text: "Contents text",
submitText: "Yes",
cancelText: "No",
onSubmit: () {
// Processing when the Yes button is pressed
},
onCancel: () {
// Processing when the No button is pressed
}
);
Responsive layout
UniversalUI allows users to create UIs across platforms with different screen sizes and orientations, such as PC and mobile, without having to be aware of the differences.
If you want to upload image files, etc., use the Storage object.
Use a file picker or similar tool to obtain the file path and byte data (UInt8List) of the file to be uploaded and pass them to the various methods.
final storage = Storage(constStorageQuery("test/file"));
final pickedData = awaitFilePicker.platform.pickFiles();
storage.upload(pickedData.first.path);
By default, these are only stored in the app's memory and will revert to their original state when the app is restarted.
If you wish to persist data, see Firebase/Firestore support below.
For other details, please see the package details page.
As for the data model (database), authentication, and file storage, the default provides the ability to store only within the app, but it can be switched to target the device local or Firebase by replacing the adapter.
If you use the adapter for Firebase/Firestore, import the following packages in advance.
# If you want to use Firebase Authentication
flutter pub add katana_auth_firebase
# If you want to use Firestore
flutter pub add katana_model_firestore
# If you want to use Cloud Storage for Firebase
flutter pub add katana_storage_firebase
Also, please complete the initial Firebase setup using FlutterFire or similar.
flutterfire configure
To switch the data model (database), authentication, and file storage to Firestore, Firebase Authentication, or Cloud Storage for Firebase, respectively, the following adapters must be replaced with the corresponding ones.
/// App Model.////// By replacing this with another adapter, the data storage location can be changed.// TODO: Change the database.// final modelAdapter = RuntimeModelAdapter();final modelAdapter = FirestoreModelAdapter(options: DefaultFirebaseOptions.currentPlatform);
/// App Auth./// /// Changing to another adapter allows you to change to another authentication mechanism.// TODO: Change the authentication.// final authAdapter = RuntimeAuthAdapter();final authAdapter = FirebaseAuthAdapter(options: DefaultFirebaseOptions.currentPlatform);
/// App Storage./// /// Changing to another adapter allows you to change to another storage mechanism.// TODO: Change the storage.// final storageAdapter = LocalStorageAdapter();final storageAdapter = FirebaseStorageAdapter(options: DefaultFirebaseOptions.currentPlatform);
You can change to Firebase without worrying about other code, just change the adapter.
Other Functions
The Masamune framework provides other useful features.
Each is offered in a separate package, so please refer to that for details.