Escolar Documentos
Profissional Documentos
Cultura Documentos
GetX:
https://github.com/jonataslaw/getx/blob/master/README.pt-br.md
1. Árvore de diretórios:
a. app
i. controller - Singleton para gestão de widgets comuns a todo projeto (ex.: Snackbar);
ii. data - Controle de acesso a dados;
1. models - Modelos das entidades;
2. preferences - Modelos das entidades em cache;
3. providers - Classes de conexão com API;
4. repositores - Tratamento dos dados de retorno da API (utilizando os models).
iii. modules - Controle das telas do projeto;
1. <pasta-da-página> - A pasta que terá a gestão da página;
a. bindings - Injeção das dependências da página;
b. controllers - Controles do "back-end" do front;
c. views - Layouts gráficos da página;
i. components - Gestão dos componentes das views.
d. pages - páginas internas da view raiz.
e.
iv. routes - Controle das rotas do projeto;
1. app_routes.dart - Controle das rotas do projeto (URLs);
2. app_pages.dart - Controle de carregamento da página e suas dependências.
v. themes - Controle de estilos do projeto (cores, fonts, etc);
vi. utils - Classes genéricas comuns a todo projeto (validators, helpers, etc);
vii. widgets - Widgets comuns a todo projeto (inputs, dialogs, buttons, text fields, etc);
Widgets:
1. Binding:
a. Instanciar o provider;
b. Instanciar o repository enviando o provider por parâmetro;
c. put no controller enviando o repository por parâmetro;
d.
d. import 'package:get/get.dart';
import '../../../../../data/providers/attribute_provider.dart';
import '../../../../../data/repositories/attribute_repository.
dart';
import '../controllers/attributes_controller.dart';
Get.put<AttributesController>(AttributesController
(repository));
}
}
2. Controller:
a. Criar uma variável com o nome “repository”, tipada com a classe do repository desejado;
b. Criar o construtor da classe e carregar a variável de repository com o parâmetro enviado na binding;
c. Declarar as variáveis (observáveis e/ou comuns);
d. Métodos:
i. onInit - Método abstrato herdado que pode ser sobrescrito, através da anotação @override, chamado ao instanciar o
controller.
ii. onReady - Método abstrato herdado que pode ser sobrescrito, através da anotação @override, chamado após build da
view.
iii. onClose - Método abstrato herdado que pode ser sobrescrito, através da anotação @override, chamado no
encerramento da view.
iv. Métodos de acesso a API (GET, POST, PUT e DELETE):
1. Criar uma Future, com ou sem retorno (dependendo da necessidade), onde é chamada a função
correspondente na classe da repository, através da variável "repository" criada e instanciada no início da
controller;
2. O retorno da repository é uma classe Either, da biblioteca Dartz https://pub.dev/packages/dartz, onde a
esquerda é recebido um ErrorResponse e a direita é recebido o model (ou List<model>).
v. Demais métodos conforme a necessidade.
e. import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../../controllers/app_controller.dart';
import '../../../../../data/models/attribute.dart';
import '../../../../../data/repositories/attribute_repository.
dart';
import '../../../../../routes/app_pages.dart';
import '../../../../../widgets/az_snackbar.dart';
AttributesController(this.repository) {
Get.delete<AttributesController>();
}
final appController = Get.find<AppController>();
Attribute? attribute;
@override
void onInit() {
refreshAttributes();
super.onInit();
}
@override
void onClose() {
textController.value.dispose();
editTextController.value.dispose();
}
response.fold(
(l) {
isLoadingAttributes.value = false;
appController.streamSnackbar.add(
AzSnackbar(
message: l.error ?? 'Erro interno',
type: AzSnackbarType.warning,
),
);
isLoadingAttributes.value = false;
},
);
}
Future<void> sortAttributesController({
VoidCallback? onSuccess,
required List<String> listSort,
}) async {
final response = await repository.sortAttributes(listSort:
listSort);
response.fold(
(l) {
appController.streamSnackbar.add(
AzSnackbar(
message: l.error ?? 'Erro interno',
type: AzSnackbarType.warning,
),
);
if (l.statusCode != null &&
(l.statusCode == 401 || (l.error ?? '').contains
('Token'))) {
Get.offAndToNamed(Routes.login);
}
},
(r) {
listSortAttributes.value = r;
if (onSuccess != null) onSuccess();
},
);
}
Future<void> toggleStatusAttribute({
required Attribute attribute,
VoidCallback? onSuccess,
}) async {
final response = await repository.toggleAttributes(
active: attribute.active!, attributeId: attribute.
sId!);
response.fold(
(l) {
appController.streamSnackbar.add(
AzSnackbar(
message: l.error ?? 'Erro interno',
type: AzSnackbarType.warning,
),
);
Future<void> deleteAttribute({
required String attributeId,
VoidCallback? onSuccess,
}) async {
final response = await repository.deleteAttribute
(attributeId: attributeId);
response.fold(
(l) {
appController.streamSnackbar.add(
AzSnackbar(
message: l.error ?? 'Erro interno',
type: AzSnackbarType.warning,
),
);
Future<void> updateAttribute({
required Attribute attribute,
VoidCallback? onSuccess,
}) async {
final response = await repository.updateAttribute(
attributeId: attribute.sId!,
value: attribute.value,
name: attribute.name!);
response.fold(
(l) {
appController.streamSnackbar.add(
AzSnackbar(
message: l.error ?? 'Erro interno',
type: AzSnackbarType.warning,
),
);
Future<void> insertAttributeValue({
required String attributeId,
required Attribute attribute,
VoidCallback? onSuccess,
}) async {
final response = await repository.insertAttributeValue(
attribute: attribute, attributeId: attributeId);
response.fold(
(l) {
appController.streamSnackbar.add(
AzSnackbar(
message: l.error ?? 'Erro interno',
type: AzSnackbarType.warning,
),
);
void findAttributes() {
listAttributes.clear();
for (var item in allAttributes) {
if ((item.parent ?? '').trim().isEmpty) {
listAttributes.add(item);
}
}
}
3. Repository:
a. Criar uma variável com nome “provider” tipada com a classe do provider desejado;
b. Criar o construtor da classe onde é instanciada a variável provider, através do binding da página (processo já descrito acima,
neste documento);
c. Criar uma Future com retorno Either<Erro, Model> ou Either<Erro, List<Model>>, da biblioteca Dartz https://pub.dev/packages
/dartz, para cada requisição necessária.
d. import 'dart:convert';
import 'package:dartz/dartz.dart';
import '../models/attribute.dart';
import '../providers/attribute_provider.dart';
import '../models/error_response_model.dart';
class AttributeRepository {
final AttributeProvider provider;
AttributeRepository(this.provider);
Future<Either<ErrorResponse, List<Attribute>>>
findAllAttributes() async {
try {
final response = await provider.findAllAttributes();
if (response.statusCode == 200) {
return right((bodyResponse['items'] as List)
.map((e) => Attribute.fromJson(e))
.toList());
} else {
return left(ErrorResponse(
error: bodyResponse['message'] ?? bodyResponse
['error'],
statusCode: response.statusCode,
));
}
} catch (e) {
return left(ErrorResponse(error: e.toString()));
}
}
if (response.statusCode == 200) {
return right(Attribute.fromJson(bodyResponse));
} else {
return left(ErrorResponse(
error: bodyResponse['message'] ?? bodyResponse
['error'],
statusCode: response.statusCode,
));
}
} catch (e) {
return left(ErrorResponse(error: e.toString()));
}
}
if (response.statusCode == 200) {
return right(Attribute.fromJson(bodyResponse));
} else {
return left(ErrorResponse(
error: bodyResponse['message'] ?? bodyResponse
['error'],
statusCode: response.statusCode,
));
}
} catch (e) {
return left(ErrorResponse(error: e.toString()));
}
}
if (response.statusCode == 200) {
return right(Attribute.fromJson(bodyResponse));
} else {
return left(ErrorResponse(
error: bodyResponse['message'] ?? bodyResponse
['error'],
statusCode: response.statusCode,
));
}
} catch (e) {
return left(ErrorResponse(error: e.toString()));
}
}
if (response.statusCode == 200) {
return right(Attribute.fromJson(bodyResponse));
} else {
return left(ErrorResponse(
error: bodyResponse['message'] ?? bodyResponse
['error'],
statusCode: response.statusCode,
));
}
} catch (e) {
return left(ErrorResponse(error: e.toString()));
}
}
if (response.statusCode == 200) {
return right((bodyResponse['removed']));
} else {
return left(ErrorResponse(
error: bodyResponse['message'] ?? bodyResponse
['error'],
statusCode: response.statusCode,
));
}
} catch (e) {
return left(ErrorResponse(error: e.toString()));
}
}
if (response.statusCode == 200) {
return right(Attribute.fromJson(bodyResponse));
} else {
return left(ErrorResponse(
error: bodyResponse['message'] ?? bodyResponse
['error'],
statusCode: response.statusCode,
));
}
} catch (e) {
return left(ErrorResponse(error: e.toString()));
}
}
}
4. Provider:
a. Criar uma Future com retorno Response, da biblioteca https://pub.dev/packages/http, para cada requisição necessária.
b. import 'dart:convert';
import '../models/attribute.dart';
import '../../utils/prefs.dart';
import '../../values/consts.dart';
import '../../../../../../../app/utils/http_helper.dart' as
http;
class AttributeProvider {
Future<http.Response> findAllAttributes() async {
String sellerId = await Prefs.getString(AppStringsConst.
userId);
String url =
AppStringsConst.findAttributes.replaceAll(':
seller_id', sellerId);
return await http.get(url);
}
Future<http.Response> insertAttributeValue(
{required String attributeId, required Attribute
attribute}) async {
String sellerId = await Prefs.getString(AppStringsConst.
userId);
String url = AppStringsConst.singleAttributes
.replaceAll(':seller_id', sellerId)
.replaceAll(':attribute_id', attributeId);
final body = {
"key": attribute.key,
"value": attribute.value,
"name": attribute.name,
"active": attribute.active
};
return await http.post(url, body: jsonEncode(body));
}
Future<http.Response> toggleAttributes(
{required String attributeId, required bool active})
async {
String sellerId = await Prefs.getString(AppStringsConst.
userId);
String url = AppStringsConst.singleAttributes
.replaceAll(':seller_id', sellerId)
.replaceAll(':attribute_id', attributeId);
final body = {"active": active};
return await http.put(url, body: jsonEncode(body));
}
Future<http.Response> updateAttribute(
{required String attributeId,
required String name,
String? value}) async {
String sellerId = await Prefs.getString(AppStringsConst.
userId);
String url = AppStringsConst.singleAttributes
.replaceAll(':seller_id', sellerId)
.replaceAll(':attribute_id', attributeId);
final body = {"name": name, "value": value};
return await http.put(url, body: jsonEncode(body));
}
Future<http.Response> findAttribute({required String
attributeId}) async {
String sellerId = await Prefs.getString(AppStringsConst.
userId);
String url = AppStringsConst.singleAttributes
.replaceAll(':seller_id', sellerId)
.replaceAll(':attribute_id', attributeId);
return await http.get(url);
}
5. Model:
a. Utilizar o retorno da API no site https://www.jsontodart.in/ e aplicar o tratamento nos campos conforme exemplo abaixo:
i. Model antes do tratamento (retorno cru do site citado acima):
1. class ProductModel {
ProductModel({
required this.description,
required this.dimensions,
required this.tags,
required this.attributes,
required this.together,
required this.categories,
required this.images,
required this._id,
required this.sellerId,
required this.active,
required this.productSellerId,
required this.name,
required this.amount,
required this.brandId,
required this.sku,
required this.ncm,
required this.ean,
required this.resale,
required this.datasheet,
required this.createdAt,
required this.updatedAt,
required this.meta,
});
late final Description description;
late final Dimensions dimensions;
late final List<String> tags;
late final List<Attributes> attributes;
late final List<Together> together;
late final List<String> categories;
late final List<Images> images;
late final String _id;
late final String sellerId;
late final bool active;
late final String productSellerId;
late final String name;
late final double amount;
late final String brandId;
late final String sku;
late final String ncm;
late final String ean;
late final bool resale;
late final List<Datasheet> datasheet;
late final String createdAt;
late final String updatedAt;
late final Meta meta;
class Description {
Description({
required this.long,
required this.short,
});
late final String long;
late final String short;
class Dimensions {
Dimensions({
required this.height,
required this.length,
required this.width,
required this.weight,
});
late final double height;
late final double length;
late final double width;
late final double weight;
class Attributes {
Attributes({
required this._id,
required this.key,
required this.value,
required this.name,
required this.parent,
required this.sellerId,
required this.sort,
required this.active,
required this.createdAt,
required this.updatedAt,
});
late final String _id;
late final String key;
late final String value;
late final String name;
late final String parent;
late final String sellerId;
late final int sort;
late final bool active;
late final String createdAt;
late final String updatedAt;
class Together {
Together({
required this.id,
required this.name,
required this.image,
});
late final String id;
late final String name;
late final String image;
class Images {
Images({
required this.url,
});
late final String url;
class Datasheet {
Datasheet({
required this.key,
required this.value,
});
late final String key;
late final String value;
class Meta {
Meta({
required this.title,
required this.description,
required this.url,
required this.keywords,
});
late final String title;
late final String description;
late final String url;
late final List<String> keywords;
1. class ProductModel {
String? id;
String? name;
String? sellerId;
String? productSellerId;
String? brandId;
String? sku;
String? ncm;
String? ean;
String? createdAt;
String? updatedAt;
bool? active;
bool? resale;
double? amount;
Description? description;
Dimensions? dimensions;
Meta? meta;
List<String>? tags;
List<String>? categories;
List<Attributes>? attributes;
List<Together>? together;
List<Images>? images;
List<Datasheet>? datasheet;
ProductModel({
this.id = '',
this.name = '',
this.sellerId = '',
this.productSellerId = '',
this.brandId = '',
this.sku = '',
this.ncm = '',
this.ean = '',
this.createdAt = '',
this.updatedAt = '',
this.active = true,
this.resale = false,
this.amount = 0.0,
this.description,
this.dimensions,
this.meta,
this.tags = const <String>[],
this.categories = const <String>[],
this.attributes = const <Attributes>[],
this.together = const <Together>[],
this.images = const <Images>[],
this.datasheet = const <Datasheet>[],
});
_data['_id'] = id ?? '';
_data['name'] = name ?? '';
_data['seller_id'] = sellerId ?? '';
_data['product_seller_id'] = productSellerId ??
'';
_data['brand_id'] = brandId ?? '';
_data['sku'] = sku ?? '';
_data['ncm'] = ncm ?? '';
_data['ean'] = ean ?? '';
_data['createdAt'] = createdAt ?? '';
_data['updatedAt'] = updatedAt ?? '';
_data['amount'] = double.tryParse(amount.
toString()) ?? 0.0;
_data['active'] = active ?? true;
_data['resale'] = resale ?? false;
_data['description'] =
description != null ? description?.toJson()
: Description();
_data['dimensions'] =
dimensions != null ? dimensions?.toJson() :
Dimensions();
_data['meta'] = meta != null ? meta?.toJson() :
Meta();
_data['tags'] = tags ?? <String>[];
_data['categories'] = categories ?? <String>[];
_data['attributes'] = attributes != null
? attributes?.map((e) => e.toJson()).toList()
: <Attributes>[];
_data['together'] = together != null
? together?.map((e) => e.toJson()).toList()
: <Together>[];
_data['images'] =
images != null ? images?.map((e) => e.
toJson()).toList() : <Images>[];
_data['datasheet'] = datasheet != null
? datasheet?.map((e) => e.toJson()).toList()
: <Datasheet>[];
return _data;
}
}
class Description {
String? long;
String? short;
Description({
this.long = '',
this.short = '',
});
return _data;
}
}
class Dimensions {
double? height;
double? length;
double? width;
double? weight;
Dimensions({
this.height = 0.0,
this.length = 0.0,
this.width = 0.0,
this.weight = 0.0,
});
_data['height'] = double.tryParse(height.
toString()) ?? 0.0;
_data['length'] = double.tryParse(length.
toString()) ?? 0.0;
_data['width'] = double.tryParse(width.
toString()) ?? 0.0;
_data['weight'] = double.tryParse(weight.
toString()) ?? 0.0;
return _data;
}
}
class Attributes {
String? id;
String? key;
String? value;
String? name;
String? parent;
String? sellerId;
String? createdAt;
String? updatedAt;
int? sort;
bool? active;
Attributes({
this.id = '',
this.key = '',
this.value = '',
this.name = '',
this.parent = '',
this.sellerId = '',
this.createdAt = '',
this.updatedAt = '',
this.sort = 0,
this.active = true,
});
_data['_id'] = id ?? '';
_data['key'] = key ?? '';
_data['value'] = value ?? '';
_data['name'] = name ?? '';
_data['parent'] = parent ?? '';
_data['seller_id'] = sellerId ?? '';
_data['createdAt'] = createdAt ?? '';
_data['updatedAt'] = updatedAt ?? '';
_data['sort'] = int.tryParse(sort.toString()) ??
0;
_data['active'] = active ?? true;
return _data;
}
}
class Together {
String? id;
String? name;
String? image;
Together({
this.id = '',
this.name = '',
this.image = '',
});
_data['id'] = id ?? '';
_data['name'] = name ?? '';
_data['image'] = image ?? '';
return _data;
}
}
class Images {
String? url;
Images({
this.url = '',
});
class Datasheet {
String? key;
String? value;
Datasheet({
this.key = '',
this.value = '',
});
return _data;
}
}
class Meta {
String? title;
String? description;
String? url;
List<String>? keywords;
Meta({
this.title = '',
this.description = '',
this.url = '',
this.keywords = const <String>[],
});
return _data;
}
}
6. View:
a. Criar a classe da view com StatelessWidget e buscar a controller com um Get.find<Controller>();
b. O build da classe deve retornar o Widget "view()" (descrito abaixo) conforme o dispositivo utilizando a classe "Responsive";
c. Criar um Widget denominado "view" que retorna a classe padrão da composição base da página “AzBasePage”;
d. Utilizar condições para separar o conteúdo mobile e desktop, reaproveitando o máximo possível dos componentes (concentrar
a regra de negócio do front na controller);
e. Componentizar, na pasta “components”, Widgets grandes para melhor compreensão do código;
f. Components:
i. Ao utilizar StatelessWidget ou StatefullWidget, lembrar de usar o "Get.find<controller>()" para pegar a controller da
memória.
g. import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../../utils/responsive_helper.dart';
import '../../../../../widgets/az_base_page.dart';
import '../../../../../widgets/az_progress_indicator.dart';
import '../controllers/attributes_controller.dart';
import 'components/add_attribute.dart';
import 'components/breadcrumbs.dart';
import 'components/list_reorderable_attribute.dart';
@override
Widget build(BuildContext context) {
BuildContext _context = context;
if (kIsWeb) {
if (Get.width < Responsive.desktopBreakpoint) {
_context = Get.context ?? context;
}
} else {
_context = Get.context ?? context;
}
return view(Responsive.isMobile(_context));
}
7. Boas práticas:
a. A fim de evitar a repetição de código, as vezes precisamos carregar a controller de outra página na página que estamos
construindo (pra acessar sua repository, provider, etc). O jeito correto de fazer isso é carregando a controller da página que
possui os métodos que queremos no binding da página em construção.
i. Obs.: JAMAIS utilizar o método Get.find para carregar a controller de outra página direto na view da página que está
sendo construída, sempre carregar como dependência no binding.
b. Sempre fazer o dispose dos TextEditingController e AnimationController para não gerar memory leaks. Caso estes estejam nas
controllers, utilizar o método onClose herdado do Get.
8. Check list:
Pasta(s) estruturada(s);
Tratar o(s) model(s);
Provider(s) padronizado(s);
Repository(ies) padronizado(s);
Usar o widget AzBasePage;
Binding(s) padronizado(s);
Controller(s) padronizado(s);
Boas práticas;
Todos os inputs devem possuir máscara e KeyboardType;
Inputs de data devem possuir DateTimePicker;
Inputs de números devem aceitar só números;
Inputs com letras maiúsculas não devem depender do caps lock;
Inputs de cep devem auto-completar o restante do endereço;
Quando o foco estiver sobre o input, o mesmo deve ficar com as bordas na cor primária e, se houver ícone dentro, este também deverá
ficar da cor primária;
Os filtros da tela devem persistir na transição para tela de detalhe e limpos ao retornar à anterior;