Você está na página 1de 31

Padrão Técnico Front-end do Projeto

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';

class AttributesBinding extends Bindings {


@override
void dependencies() {
final provider = AttributeProvider();
final repository = AttributeRepository(provider);

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';

class AttributesController extends GetxController {


final AttributeRepository repository;

AttributesController(this.repository) {
Get.delete<AttributesController>();
}
final appController = Get.find<AppController>();

RxList<Attribute> allAttributes = <Attribute>[].obs;


RxList<Attribute> listAttributes = <Attribute>[].obs;
RxList<Attribute> listSortAttributes = <Attribute>[].obs;
Rx<TextEditingController> textController =
TextEditingController().obs;
Rx<TextEditingController> editTextController =
TextEditingController().obs;
final RxBool isLoading = false.obs;
final RxBool isLoadingAttributes = false.obs;

final GlobalKey<FormState> attributeformKey =


GlobalKey<FormState>();

Attribute? attribute;

@override
void onInit() {
refreshAttributes();
super.onInit();
}

@override
void onClose() {
textController.value.dispose();
editTextController.value.dispose();
}

void refreshAttributes() async {


listAttributes.clear();
await findAllAttributes(onSuccess: () {
findAttributes();
});
}

Future<void> findAllAttributes({VoidCallback? onSuccess})


async {
isLoadingAttributes.value = true;

final response = await repository.findAllAttributes();

response.fold(
(l) {
isLoadingAttributes.value = false;

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) {
allAttributes.value = r;

if (onSuccess != null) onSuccess();

isLoadingAttributes.value = false;
},
);
}

Future<void> sortAttributes(int oldIndex, int newIndex)


async {
if (oldIndex < newIndex) newIndex -= 1;

final Attribute item = listAttributes.removeAt(oldIndex);


listAttributes.insert(newIndex, item);

List<String> listSort = [];


for (var item in listAttributes) {
listSort.add(item.sId!);
}

await sortAttributesController(listSort: listSort);


}

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,
),
);

if (l.statusCode != null &&


(l.statusCode == 401 || (l.error ?? '').contains
('Token'))) {
Get.offAndToNamed(Routes.login);
}
},
(r) {
if (onSuccess != null) {
onSuccess();
//refreshAttributes();
}
},
);
}

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,
),
);

if (l.statusCode != null &&


(l.statusCode == 401 || (l.error ?? '').contains
('Token'))) {
Get.offAndToNamed(Routes.login);
}
},
(r) {
if (onSuccess != null) {
onSuccess();
}
},
);
}

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,
),
);

if (l.statusCode != null &&


(l.statusCode == 401 || (l.error ?? '').contains
('Token'))) {
Get.offAndToNamed(Routes.login);
}
},
(r) {
attribute = r;
if (onSuccess != null) {
onSuccess();
}
},
);
}

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,
),
);

if (l.statusCode != null &&


(l.statusCode == 401 || (l.error ?? '').contains
('Token'))) {
Get.offAndToNamed(Routes.login);
}
},
(r) {
if (onSuccess != null) {
onSuccess();
refreshAttributes();
}
},
);
}

void findAttributes() {
listAttributes.clear();
for (var item in allAttributes) {
if ((item.parent ?? '').trim().isEmpty) {
listAttributes.add(item);
}
}
}

List<Attribute> findAttributesFromParent(String parent) {


return allAttributes.where((element) => element.parent ==
parent).toList();
}
}

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();

final bodyResponse = jsonDecode(response.body);

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()));
}
}

Future<Either<ErrorResponse, Attribute>> insertAttribute(


{required Attribute attribute}) async {
try {
final response = await provider.insertAttribute
(attribute: attribute);
final bodyResponse = jsonDecode(response.body);

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()));
}
}

Future<Either<ErrorResponse, Attribute>> insertAttributeValue


(
{required String attributeId, required Attribute
attribute}) async {
try {
final response = await provider.insertAttributeValue(
attribute: attribute, attributeId: attributeId);

final bodyResponse = jsonDecode(response.body);

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()));
}
}

Future<Either<ErrorResponse, List<Attribute>>> sortAttributes


(
{required List<String> listSort}) async {
try {
final response = await provider.sortAttributes(listSort:
listSort);

final bodyResponse = jsonDecode(response.body);


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()));
}
}

Future<Either<ErrorResponse, Attribute>> toggleAttributes(


{required String attributeId, required bool active})
async {
try {
final response = await provider.toggleAttributes(
attributeId: attributeId, active: active);

final bodyResponse = jsonDecode(response.body);

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()));
}
}

Future<Either<ErrorResponse, Attribute>> findAttribute(


{required String attributeId}) async {
try {
final response = await provider.findAttribute
(attributeId: attributeId);

final bodyResponse = jsonDecode(response.body);

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()));
}
}

Future<Either<ErrorResponse, bool>> deleteAttribute(


{required String attributeId}) async {
try {
final response = await provider.deleteAttribute
(attributeId: attributeId);

final bodyResponse = jsonDecode(response.body);

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()));
}
}

Future<Either<ErrorResponse, Attribute>> updateAttribute(


{required String attributeId,
required String name,
String? value}) async {
try {
final response = await provider.updateAttribute(
attributeId: attributeId,
name: name,
value: value,
);

final bodyResponse = jsonDecode(response.body);

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> insertAttribute({required Attribute


attribute}) async {
String sellerId = await Prefs.getString(AppStringsConst.
userId);
String url =
AppStringsConst.findAttributes.replaceAll(':
seller_id', sellerId);
final body = {
"key": attribute.key,
"value": attribute.value,
"name": attribute.name,
"sort": attribute.sort,
};
return await http.post(url, body: jsonEncode(body));
}

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> sortAttributes({required List<String>


listSort}) async {
String sellerId = await Prefs.getString(AppStringsConst.
userId);
String url =
AppStringsConst.sortAttributes.replaceAll(':
seller_id', sellerId);
return await http.put(url, body: jsonEncode(listSort));
}

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);
}

Future<http.Response> deleteAttribute({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.delete(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;

ProductModel.fromJson(Map<String, dynamic> json){


description = Description.fromJson(json
['description']);
dimensions = Dimensions.fromJson(json
['dimensions']);
tags = List.castFrom<dynamic, String>(json
['tags']);
attributes = List.from(json['attributes']).map
((e)=>Attributes.fromJson(e)).toList();
together = List.from(json['together']).map((e)
=>Together.fromJson(e)).toList();
categories = List.castFrom<dynamic, String>(json
['categories']);
images = List.from(json['images']).map((e)
=>Images.fromJson(e)).toList();
_id = json['_id'];
sellerId = json['seller_id'];
active = json['active'];
productSellerId = json['product_seller_id'];
name = json['name'];
amount = json['amount'];
brandId = json['brand_id'];
sku = json['sku'];
ncm = json['ncm'];
ean = json['ean'];
resale = json['resale'];
datasheet = List.from(json['datasheet']).map((e)
=>Datasheet.fromJson(e)).toList();
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
meta = Meta.fromJson(json['meta']);
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['description'] = description.toJson();
_data['dimensions'] = dimensions.toJson();
_data['tags'] = tags;
_data['attributes'] = attributes.map((e)=>e.
toJson()).toList();
_data['together'] = together.map((e)=>e.
toJson()).toList();
_data['categories'] = categories;
_data['images'] = images.map((e)=>e.toJson()).
toList();
_data['_id'] = _id;
_data['seller_id'] = sellerId;
_data['active'] = active;
_data['product_seller_id'] = productSellerId;
_data['name'] = name;
_data['amount'] = amount;
_data['brand_id'] = brandId;
_data['sku'] = sku;
_data['ncm'] = ncm;
_data['ean'] = ean;
_data['resale'] = resale;
_data['datasheet'] = datasheet.map((e)=>e.
toJson()).toList();
_data['createdAt'] = createdAt;
_data['updatedAt'] = updatedAt;
_data['meta'] = meta.toJson();
return _data;
}
}

class Description {
Description({
required this.long,
required this.short,
});
late final String long;
late final String short;

Description.fromJson(Map<String, dynamic> json){


long = json['long'];
short = json['short'];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['long'] = long;
_data['short'] = short;
return _data;
}
}

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;

Dimensions.fromJson(Map<String, dynamic> json){


height = json['height'];
length = json['length'];
width = json['width'];
weight = json['weight'];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['height'] = height;
_data['length'] = length;
_data['width'] = width;
_data['weight'] = weight;
return _data;
}
}

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;

Attributes.fromJson(Map<String, dynamic> json){


_id = json['_id'];
key = json['key'];
value = json['value'];
name = json['name'];
parent = json['parent'];
sellerId = json['seller_id'];
sort = json['sort'];
active = json['active'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['_id'] = _id;
_data['key'] = key;
_data['value'] = value;
_data['name'] = name;
_data['parent'] = parent;
_data['seller_id'] = sellerId;
_data['sort'] = sort;
_data['active'] = active;
_data['createdAt'] = createdAt;
_data['updatedAt'] = updatedAt;
return _data;
}
}

class Together {
Together({
required this.id,
required this.name,
required this.image,
});
late final String id;
late final String name;
late final String image;

Together.fromJson(Map<String, dynamic> json){


id = json['id'];
name = json['name'];
image = json['image'];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['id'] = id;
_data['name'] = name;
_data['image'] = image;
return _data;
}
}

class Images {
Images({
required this.url,
});
late final String url;

Images.fromJson(Map<String, dynamic> json){


url = json['url'];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['url'] = url;
return _data;
}
}

class Datasheet {
Datasheet({
required this.key,
required this.value,
});
late final String key;
late final String value;

Datasheet.fromJson(Map<String, dynamic> json){


key = json['key'];
value = json['value'];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['key'] = key;
_data['value'] = value;
return _data;
}
}

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;

Meta.fromJson(Map<String, dynamic> json){


title = json['title'];
description = json['description'];
url = json['url'];
keywords = List.castFrom<dynamic, String>(json
['keywords']);
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};
_data['title'] = title;
_data['description'] = description;
_data['url'] = url;
_data['keywords'] = keywords;
return _data;
}
}

ii. Model depois do tratamento:

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>[],
});

ProductModel.fromJson(Map<String, dynamic> json) {


id = json['_id'] ?? '';
name = json['name'] ?? '';
sellerId = json['seller_id'] ?? '';
productSellerId = json['product_seller_id'] ??
'';
brandId = json['brand_id'] ?? '';
sku = json['sku'] ?? '';
ncm = json['ncm'] ?? '';
ean = json['ean'] ?? '';
active = json['active'] ?? true;
resale = json['resale'] ?? false;
amount = double.tryParse(json['amount'].
toString()) ?? 0.0;
createdAt = json['createdAt'] ?? '';
updatedAt = json['updatedAt'] ?? '';
description = json['description'] != null
? Description.fromJson(json['description'])
: Description();
dimensions = json['dimensions'] != null
? Dimensions.fromJson(json['dimensions'])
: Dimensions();
meta = json['meta'] != null ? Meta.fromJson(json
['meta']) : Meta();
tags = json['tags'] != null
? List.castFrom<dynamic, String>(json
['tags'])
: <String>[];
categories = json['categories'] != null
? List.castFrom<dynamic, String>(json
['categories'])
: <String>[];
attributes = json['attributes'] != null
? List.from(json['attributes'])
.map((e) => Attributes.fromJson(e))
.toList()
: <Attributes>[];
together = json['together'] != null
? List.from(json['together']).map((e) =>
Together.fromJson(e)).toList()
: <Together>[];
images = json['images'] != null
? List.from(json['images']).map((e) =>
Images.fromJson(e)).toList()
: <Images>[];
datasheet = json['datasheet'] != null
? List.from(json['datasheet'])
.map((e) => Datasheet.fromJson(e))
.toList()
: <Datasheet>[];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_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 = '',
});

Description.fromJson(Map<String, dynamic> json) {


long = json['long'] ?? '';
short = json['short'] ?? '';
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_data['long'] = long ?? '';


_data['short'] = 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,
});

Dimensions.fromJson(Map<String, dynamic> json) {


height = double.tryParse(json['height'].
toString()) ?? 0.0;
length = double.tryParse(json['length'].
toString()) ?? 0.0;
width = double.tryParse(json['width'].toString())
?? 0.0;
weight = double.tryParse(json['weight'].
toString()) ?? 0.0;
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_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,
});

Attributes.fromJson(Map<String, dynamic> json) {


id = json['_id'] ?? '';
key = json['key'] ?? '';
value = json['value'] ?? '';
name = json['name'] ?? '';
parent = json['parent'] ?? '';
sellerId = json['seller_id'] ?? '';
createdAt = json['createdAt'] ?? '';
updatedAt = json['updatedAt'] ?? '';
sort = int.tryParse(json['sort'].toString()) ??
0;
active = json['active'] ?? true;
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_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 = '',
});

Together.fromJson(Map<String, dynamic> json) {


id = json['id'] ?? '';
name = json['name'] ?? '';
image = json['image'] ?? '';
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_data['id'] = id ?? '';
_data['name'] = name ?? '';
_data['image'] = image ?? '';

return _data;
}
}

class Images {
String? url;

Images({
this.url = '',
});

Images.fromJson(Map<String, dynamic> json) {


url = json['url'] ?? '';
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_data['url'] = url ?? '';


return _data;
}
}

class Datasheet {
String? key;
String? value;

Datasheet({
this.key = '',
this.value = '',
});

Datasheet.fromJson(Map<String, dynamic> json) {


key = json['key'] ?? '';
value = json['value'] ?? '';
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_data['key'] = key ?? '';


_data['value'] = 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>[],
});

Meta.fromJson(Map<String, dynamic> json) {


title = json['title'] ?? '';
description = json['description'] ?? '';
url = json['url'] ?? '';
keywords = json['keywords'] != null
? List.castFrom<dynamic, String>(json
['keywords'])
: <String>[];
}

Map<String, dynamic> toJson() {


final _data = <String, dynamic>{};

_data['title'] = title ?? '';


_data['description'] = description ?? '';
_data['url'] = url ?? '';
_data['keywords'] = keywords ?? <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';

class AttributesView extends StatelessWidget {


AttributesView({Key? key}) : super(key: key);

final controller = Get.find<AttributesController>();

@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));
}

view(bool isMobile) => AzBasePage(


isMobile: isMobile,
routeHeader: 'management-products',
routeSidebar: 'management-products',
content: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Breadcrumbs(),
SizedBox(height: 24),
AddAttribute(),
SizedBox(height: 8),
Obx(
() => controller.isLoadingAttributes.value
? Container(
margin: EdgeInsets.only(top: Get.height
* .3),
child: Center(child:
AzProgressIndicator()),
)
: ListReorderableAttributes(isMobile:
isMobile),
),
SizedBox(height: 46),
],
),
);
}

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;

Você também pode gostar